Browse Source

Merge pull request #1 from dtm-labs/main

merge latest
pull/312/head
StandHeo 4 years ago
committed by GitHub
parent
commit
987d6634cf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      .github/workflows/release.yml
  2. 10
      .github/workflows/tests.yml
  3. 2
      .gitignore
  4. 12
      README.md
  5. 1
      admin/.env
  6. 0
      admin/.eslintrc.js
  7. 0
      admin/.gitignore
  8. 0
      admin/README.md
  9. 1
      admin/dist/placeholder
  10. 0
      admin/index.html
  11. 3
      admin/package.json
  12. 0
      admin/postcss.config.js
  13. 0
      admin/public/favicon.ico
  14. 0
      admin/src/App.vue
  15. 38
      admin/src/api/api_dtm.ts
  16. 0
      admin/src/assets/css/index.css
  17. 4
      admin/src/components.d.ts
  18. 40
      admin/src/components/Screenfull/index.vue
  19. 2
      admin/src/components/SvgIcon/index.vue
  20. 0
      admin/src/icons/readme.md
  21. 1
      admin/src/icons/svg/fullscreen.svg
  22. 8
      admin/src/icons/svg/logo.svg
  23. 19
      admin/src/layout/aside.vue
  24. 39
      admin/src/layout/components/content.vue
  25. 15
      admin/src/layout/components/header.vue
  26. 0
      admin/src/layout/components/sidebar.vue
  27. 0
      admin/src/layout/index.vue
  28. 33
      admin/src/main.ts
  29. 0
      admin/src/permission.ts
  30. 0
      admin/src/router/asyncRouter.ts
  31. 59
      admin/src/router/index.ts
  32. 0
      admin/src/store/index.ts
  33. 14
      admin/src/store/modules/layout.ts
  34. 9
      admin/src/type/index.d.ts
  35. 0
      admin/src/type/shim.vue.d.ts
  36. 12
      admin/src/type/store/layout.ts
  37. 1
      admin/src/utils/request.ts
  38. 8
      admin/src/utils/util.ts
  39. 59
      admin/src/views/Dashboard/GlobalTransactions/AllTransactions.vue
  40. 0
      admin/src/views/Dashboard/GlobalTransactions/UnfinishedTransactions.vue
  41. 117
      admin/src/views/Dashboard/GlobalTransactions/_Components/DialogTransactionDetail.vue
  42. 0
      admin/src/views/Dashboard/Nodes/LivingNodes.vue
  43. 0
      admin/tailwind.config.js
  44. 0
      admin/tsconfig.json
  45. 4
      admin/vite.config.ts
  46. 5
      admin/yarn.lock
  47. 3
      conf.sample.yml
  48. 2
      dashboard/.env.development
  49. 17
      dashboard/src/layout/components/content.vue
  50. 13
      dashboard/src/main.ts
  51. 59
      dashboard/src/router/index.ts
  52. 20
      dtmcli/barrier.go
  53. 7
      dtmcli/dtmimp/db_special.go
  54. 4
      dtmcli/dtmimp/db_special_test.go
  55. 12
      dtmcli/dtmimp/trans_xa_base.go
  56. 16
      dtmcli/dtmimp/utils.go
  57. 4
      dtmgrpc/type.go
  58. 3
      dtmsvr/api.go
  59. 6
      dtmsvr/api_http.go
  60. 13
      dtmsvr/config/config.go
  61. 2
      dtmsvr/config/config_utils.go
  62. 21
      dtmsvr/entry/main.go
  63. 41
      dtmsvr/storage/boltdb/boltdb.go
  64. 8
      dtmsvr/storage/redis/redis.go
  65. 2
      dtmsvr/storage/registry/registry.go
  66. 81
      dtmsvr/storage/sql/sql.go
  67. 2
      dtmsvr/storage/store.go
  68. 12
      dtmsvr/storage/trans.go
  69. 22
      dtmsvr/trans_status.go
  70. 16
      dtmsvr/trans_type_saga.go
  71. 4
      dtmsvr/trans_type_tcc.go
  72. 4
      dtmsvr/trans_type_xa.go
  73. 2
      dtmutil/db.go
  74. 4
      dtmutil/utils.go
  75. 13
      go.mod
  76. 100
      go.sum
  77. 6
      helper/.goreleaser.yml
  78. 11
      helper/Dockerfile-release
  79. 73
      helper/README-cn.md
  80. 10
      helper/README-en.md
  81. 16
      helper/bench/svr/http.go
  82. 28
      helper/compose.cloud.yml
  83. 16
      helper/compose.mysql.yml
  84. 13
      helper/compose.postgres.yml
  85. 1
      helper/compose.store.yml
  86. 2
      helper/golint.sh
  87. 2
      helper/test-cover.sh
  88. 71
      main.go
  89. 13
      sqls/dtmsvr.storage.mysql.sql
  90. 30
      sqls/dtmsvr.storage.postgres.sql
  91. 1
      sqls/dtmsvr.storage.tdsql.sql
  92. 6
      test/api_test.go
  93. 4
      test/base_test.go
  94. 34
      test/busi/barrier.go
  95. 5
      test/busi/base_http.go
  96. 1
      test/busi/base_types.go
  97. 20
      test/busi/busi.go
  98. 3
      test/busi/quick_start.go
  99. 6
      test/common_test.go
  100. 4
      test/dtmsvr_test.go

44
.github/workflows/release.yml

@ -9,8 +9,14 @@ jobs:
name: Release on GitHub
runs-on: ubuntu-latest
steps:
- name: Get Release Version
run: |
export RELEASE_VERSION=${GITHUB_REF#refs/*/}
echo RELEASE_VERSION: ${RELEASE_VERSION}
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> $GITHUB_ENV
- name: Check out code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Validates GO releaser config
uses: docker://goreleaser/goreleaser:v1.7.0
@ -24,7 +30,38 @@ jobs:
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Docker meta
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 14
- name: Build admin
run: |
cd admin
npm install -g yarn
yarn
VITE_ADMIN_VERSION=${{ env.RELEASE_VERSION }} yarn build
cd ..
- name: Scp admin
env:
host: 'ubuntu@en.dtm.pub'
host2: 'ubuntu@dtm.pub'
dest: '/data/dtm-admin/'
run: |
cd admin
echo "${{secrets.DEPLOY_KEY}}" > deploy_key
chmod 600 ./deploy_key
tar -cvzf dist.tar.gz dist
scp -i deploy_key -o StrictHostKeyChecking=no dist.tar.gz ${{env.host}}:${{env.dest}}
ssh -i deploy_key -o StrictHostKeyChecking=no ${{env.host}} 'cd ${{env.dest}} && tar -zvxf dist.tar.gz'
scp -i deploy_key -o StrictHostKeyChecking=no dist.tar.gz ${{env.host2}}:${{env.dest}}
ssh -i deploy_key -o StrictHostKeyChecking=no ${{env.host2}} 'cd ${{env.dest}} && tar -zvxf dist.tar.gz'
rm deploy_key dist.tar.gz
echo > dist/placeholder
cd ..
- name: Create Docker
id: meta
uses: docker/metadata-action@v3
with:
@ -48,9 +85,6 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get Release Version
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Build and push
uses: docker/build-push-action@v2
with:

10
.github/workflows/tests.yml

@ -28,6 +28,16 @@ jobs:
- /etc/timezone:/etc/timezone:ro
ports:
- 6379:6379
postgres:
image: 'yedf/postgres-xa'
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
env:
POSTGRES_PASSWORD: mysecretpassword
POSTGRES_DB: dtm
ports:
- '5432:5432'
mongo:
image: 'yedf/mongo-rs'
volumes:

2
.gitignore

@ -1,7 +1,7 @@
conf.yml
*.out
*.log
dist
# dist
.idea/**
.vscode
default.etcd

12
README.md

@ -15,13 +15,17 @@ DTM is a distributed transaction framework which provides cross-service eventual
<img alt="function-picture" src="https://en.dtm.pub/assets/function.7d5618f8.png" height=250 />
## Features
## Who's using DTM (partial)
[Tencent](https://www.tencent.com/)
[Tencent](https://en.dtm.pub/other/using.html#tencent)
[Bytedance](https://en.dtm.pub/other/using.html#bytedance)
[Bytedance](https://www.bytedance.com/)
[Ivydad](https://en.dtm.pub/other/using.html#ivydad)
[Ivydad](https://ivydad.com)
[More](https://en.dtm.pub/other/using.html)
## [Cook Book](https://en.dtm.pub)
@ -54,7 +58,7 @@ go run main.go
DtmServer := "http://localhost:36789/api/dtmsvr"
req := &gin.H{"amount": 30} // micro-service payload
// DtmServer is the address of DTM micro-service
saga := dtmcli.NewSaga(DtmServer, dtmcli.MustGenGid(DtmServer)).
saga := dtmcli.NewSaga(DtmServer, shortuuid.New()).
// add a TransOut subtraction,forward operation with url: qsBusi+"/TransOut", reverse compensation operation with url: qsBusi+"/TransOutCom"
Add(qsBusi+"/TransOut", qsBusi+"/TransOutCom", req).
// add a TransIn subtraction, forward operation with url: qsBusi+"/TransIn", reverse compensation operation with url: qsBusi+"/TransInCom"

1
admin/.env

@ -0,0 +1 @@
VITE_ADMIN_VERSION="v0.0.0-dev"

0
dashboard/.eslintrc.js → admin/.eslintrc.js

0
dashboard/.gitignore → admin/.gitignore

0
dashboard/README.md → admin/README.md

1
admin/dist/placeholder

@ -0,0 +1 @@

0
dashboard/index.html → admin/index.html

3
dashboard/package.json → admin/package.json

@ -4,7 +4,7 @@
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "vite build && echo > dist/placeholder",
"preview": "vite preview"
},
"dependencies": {
@ -28,6 +28,7 @@
"postcss-import": "^14.1.0",
"postcss-nested": "^5.0.6",
"postcss-simple-vars": "^6.0.3",
"screenfull": "^6.0.1",
"tailwindcss": "^3.0.24",
"typescript": "^4.5.4",
"unplugin-vue-components": "^0.19.3",

0
dashboard/postcss.config.js → admin/postcss.config.js

0
dashboard/public/favicon.ico → admin/public/favicon.ico

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

0
dashboard/src/App.vue → admin/src/App.vue

38
admin/src/api/api_dtm.ts

@ -0,0 +1,38 @@
import { AxiosResponse } from 'axios'
import request from '/@/utils/request'
export interface IListAllTransactionsReq {
limit: number
position?: string
}
export function listAllTransactions<T>(payload: IListAllTransactionsReq): Promise<AxiosResponse<T>> {
return request({
url: '/api/dtmsvr/all',
method: 'get',
params: payload
})
}
export function forceStopTransaction(gid: string): Promise<AxiosResponse> {
return request({
url: '/api/dtmsvr/forceStop',
method: 'post',
data: { gid },
})
}
export function getTransaction<T>(payload: { gid: string }): Promise<AxiosResponse<T>> {
return request({
url: '/api/dtmsvr/query',
method: 'get',
params: payload
})
}
export function getDtmVersion(): Promise<AxiosResponse<any>> {
return request({
url: '/api/dtmsvr/version',
method: 'get',
})
}

0
dashboard/src/assets/css/index.css → admin/src/assets/css/index.css

4
dashboard/src/components.d.ts → admin/src/components.d.ts

@ -5,6 +5,7 @@ import '@vue/runtime-core'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AAlert: typeof import('ant-design-vue/es')['Alert']
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
AButton: typeof import('ant-design-vue/es')['Button']
@ -14,11 +15,14 @@ declare module '@vue/runtime-core' {
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AModal: typeof import('ant-design-vue/es')['Modal']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ATable: typeof import('ant-design-vue/es')['Table']
ATag: typeof import('ant-design-vue/es')['Tag']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Screenfull: typeof import('./components/Screenfull/index.vue')['default']
SvgIcon: typeof import('./components/SvgIcon/index.vue')['default']
}
}

40
admin/src/components/Screenfull/index.vue

@ -0,0 +1,40 @@
<template>
<div class="hidden-xs-only px-2">
<svg-icon v-if="!isFulScreen" class-name="cursor-pointer" icon-class="svg-fullscreen" @click="changeScreenfull(identity)" />
<svg-icon v-else class-name="cursor-pointer" icon-class="svg-exit-fullscreen" @click="changeScreenfull(identity)" />
</div>
</template>
<script setup lang='ts'>
import { notification } from 'ant-design-vue';
import { onMounted, onUnmounted, ref } from 'vue'
import screenfull from 'screenfull'
const isFulScreen = ref(false)
const changeScreenfull = (identity: string) => {
if (!screenfull.isEnabled) {
notification.open({
message: '浏览器不支持全屏',
type: 'warning'
})
} else if (identity) {
const element = document.getElementById(identity)
screenfull.toggle(element as HTMLElement)
} else {
screenfull.toggle()
}
}
const change = () => {
if (screenfull.isEnabled) isFulScreen.value = screenfull.isFullscreen
}
defineProps({
identity: {
type: String,
default: null
}
})
const emits = defineEmits(['screen'])
onMounted(() => screenfull.isEnabled && screenfull.on('change', change) && emits('screen'))
onUnmounted(() => screenfull.isEnabled && screenfull.off('change', change))
</script>

2
dashboard/src/components/SvgIcon/index.vue → admin/src/components/SvgIcon/index.vue

@ -30,6 +30,8 @@ const styleExternalIcon = () => {
<style lang="postcss" scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;

0
dashboard/src/icons/readme.md → admin/src/icons/readme.md

1
admin/src/icons/svg/fullscreen.svg

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>

After

Width:  |  Height:  |  Size: 422 B

8
admin/src/icons/svg/logo.svg

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>画板</title>
<g id="画板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<circle id="椭圆形" fill="#FF6400" cx="100" cy="100" r="100"></circle>
<path d="M108.718018,155.765918 C125.450635,155.765918 137.824756,148.87749 145.840381,135.100635 C151.751904,124.880713 154.707666,112.406396 154.707666,97.6776855 C154.707666,91.8663574 154.006299,85.7544434 152.603564,79.3419434 C151.20083,72.9294434 148.570703,66.9678223 144.713184,61.4570801 C139.903809,54.6437988 133.616553,49.959668 125.851416,47.4046875 C121.292529,45.9017578 115.581396,45.1001953 108.718018,45 L108.718018,45 L61,45 L61,155.765918 L108.718018,155.765918 Z M104.8104,136.528418 L83.4687988,136.528418 L83.4687988,64.2375 L104.8104,64.2375 C115.330908,64.2375 122.357104,67.2558838 125.888989,73.2926514 C129.420874,79.3294189 131.186816,87.9837891 131.186816,99.2557617 C131.186816,107.42168 130.009521,114.4604 127.654932,120.371924 C123.346533,131.14292 115.731689,136.528418 104.8104,136.528418 L104.8104,136.528418 Z" id="D" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

19
dashboard/src/layout/aside.vue → admin/src/layout/aside.vue

@ -4,6 +4,7 @@
<Sidebar />
</a-layout-sider>
<a-layout style="padding: 0 24px 24px">
<div v-if="layout.dtmVersion && layout.dtmVersion != dashVer" style="color:#f00"> !!! admin version: {{dashVer}} != dtm version: {{layout.dtmVersion}}. </div>
<a-breadcrumb style="margin: 16px 0">
<a-breadcrumb-item>{{ mainNav }}</a-breadcrumb-item>
<a-breadcrumb-item>{{ subNav }}</a-breadcrumb-item>
@ -26,18 +27,21 @@ import { useLayoutStore } from '../store/modules/layout'
import { IMenubarList } from '../type/store/layout'
import { findCurrentMenubar } from '../utils/util'
import { computed, onMounted, ref } from 'vue'
import { getDtmVersion } from '../api/api_dtm'
const dashVer = import.meta.env.VITE_ADMIN_VERSION
const route = useRoute()
const { getMenubar } = useLayoutStore()
const layout = useLayoutStore()
const mainNav = computed(() => {
const currentMenubar = findCurrentMenubar(getMenubar.menuList, true)
const currentMenubar = findCurrentMenubar(layout.getMenubar.menuList, true)
return currentMenubar?.meta.title
})
const subNav = computed(() => {
let subNav = ''
const currentMenubar = findCurrentMenubar(getMenubar.menuList, true)
const currentMenubar = findCurrentMenubar(layout.getMenubar.menuList, true)
currentMenubar.children?.forEach(v => {
if (route.path.indexOf(v.path) !== -1) {
subNav = v.meta.title
@ -49,17 +53,22 @@ const subNav = computed(() => {
const page = computed(() => {
let page = ''
const currentMenubar = findCurrentMenubar(getMenubar.menuList, true)
const currentMenubar = findCurrentMenubar(layout.getMenubar.menuList, true)
currentMenubar.children?.forEach(v => {
v.children?.forEach(vv => {
if (route.path == vv.path) {
page = vv.meta.title
}
})
})
})
return page
})
onMounted(() => {
layout.loadDtmVersion()
})
</script>
<style lang="postcss" scoped>

39
admin/src/layout/components/content.vue

@ -0,0 +1,39 @@
<template>
<router-view v-slot="{Component}">
<transition name="fade-transform" mode="out-in">
<keep-alive>
<div>
<a-alert
v-if="errLines[0]"
type="error"
closable
@close="onClose"
>
<template v-slot:description>
<span v-for="ln of errLines">{{ln}} <br/> </span>
</template>
</a-alert>
<component :is="Component" :key="key" />
</div>
</keep-alive>
</transition>
</router-view>
</template>
<script setup lang='ts'>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { sleep } from '/@/utils/util'
import { useLayoutStore } from '/@/store/modules/layout'
const route = useRoute()
const key = computed(() => route.path)
const layoutStore = useLayoutStore()
const errLines = computed(() => layoutStore.globalError.split("\n"))
const onClose = async (e: MouseEvent) => {
await sleep(1000)
layoutStore.setGlobalError("")
}
</script>

15
dashboard/src/layout/components/header.vue → admin/src/layout/components/header.vue

@ -1,7 +1,10 @@
<template>
<div>
<a-layout-header class="header">
<svg-icon class="logo" style="width: 36px; height: 36px;" icon-class="svg-dtm" />
<a-layout-header class="header flex">
<div class="flex items-center logo h-16">
<svg-icon style="width: 36px; height: 36px; margin-right: 84px;" icon-class="svg-logo" />
<span class="text-gray-400 text-lg">DTM admin {{ version }}</span>
</div>
<a-menu
v-model:selectedKeys="activeMenu"
theme="dark"
@ -21,8 +24,9 @@ import { useLayoutStore } from '/@/store/modules/layout'
const route = useRoute()
const router = useRouter()
const { getMenubar } = useLayoutStore()
const firstRedirectPath = '/dashboard'
const { getMenubar, getCurrentVersion } = useLayoutStore()
const firstRedirectPath = '/admin'
const version = import.meta.env.VITE_ADMIN_VERSION
const activeMenu = ref([route.meta.activeMenu !== firstRedirectPath ? route.meta.activeMenu : '/'])
@ -33,7 +37,6 @@ const onOpenChange = (d:any) => {
<style scoped>
.logo {
float: left;
margin: 16px 84px 16px 0;
margin-right: 20px;
}
</style>

0
dashboard/src/layout/components/sidebar.vue → admin/src/layout/components/sidebar.vue

0
dashboard/src/layout/index.vue → admin/src/layout/index.vue

33
admin/src/main.ts

@ -0,0 +1,33 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from '/@/router/index'
import { pinia } from '/@/store'
import { useLayoutStore } from '/@/store/modules/layout'
import '/@/permission'
import '/@/assets/css/index.css'
import 'virtual:svg-icons-register'
const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')
window.onunhandledrejection = (ev: PromiseRejectionEvent) => {
showAlert(ev.reason.stack || ev.reason.message)
}
window.onerror = err => {
if (typeof err === "string") {
return showAlert(err)
}
showAlert(JSON.stringify(err))
}
function showAlert(msg: string) {
let layout = useLayoutStore()
if (!layout.globalError) {
layout.setGlobalError(msg)
}
}

0
dashboard/src/permission.ts → admin/src/permission.ts

0
dashboard/src/router/asyncRouter.ts → admin/src/router/asyncRouter.ts

59
admin/src/router/index.ts

@ -0,0 +1,59 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { IMenubarList } from '../type/store/layout';
import { components } from './asyncRouter';
const Components: IObject<() => Promise<typeof import('*.vue')>> = Object.assign({}, components, {
LayoutHeader: (() => import('/@/layout/index.vue')) as unknown as () => Promise<typeof import('*.vue')>,
LayoutMain: (() => import('/@/layout/aside.vue')) as unknown as () => Promise<typeof import('*.vue')>
})
export const allowRouter: Array<IMenubarList> = [
{
name: 'Admin',
path: '/',
redirect: '/admin/global-transactions/all',
component: Components['LayoutHeader'],
meta: { title: 'Admin', activeMenu: '/admin' },
children: [
{
// name: 'Nodes',
// path: '/admin/nodes',
// component: Components['LayoutMain'],
// meta: { title: 'Nodes' },
// children: [
// {
// name: 'LivingNodes',
// path: '/admin/nodes/living',
// component: Components['LivingNodes'],
// meta: { title: 'Living Nodes' },
// }
// ]
// }, {
name: 'GlobalTransactions',
path: '/admin/global-transactions',
component: Components['LayoutMain'],
meta: { title: 'Global Transactions' },
children: [
{
name: 'AllTransactions',
path: '/admin/global-transactions/all',
component: Components['AllTransactions'],
meta: { title: 'All Transactions' },
// }, {
// name: 'UnfinishedTransactions',
// path: '/admin/global-transactions/unfinished',
// component: Components['UnfinishedTransactions'],
// meta: { title: 'Unfinished Transactions' },
}
]
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes: allowRouter as RouteRecordRaw[]
})
export default router

0
dashboard/src/store/index.ts → admin/src/store/index.ts

14
dashboard/src/store/modules/layout.ts → admin/src/store/modules/layout.ts

@ -1,17 +1,19 @@
import { defineStore } from 'pinia';
import { allowRouter } from '/@/router';
import { ILayout, IMenubar, IMenubarList, IStatus } from '/@/type/store/layout';
import { findCurrentMenubar } from '/@/utils/util';
import { getDtmVersion } from '/@/api/api_dtm';
export const useLayoutStore = defineStore({
id: 'layout',
state: ():ILayout => ({
state: (): ILayout => ({
menubar: {
menuList: []
},
status: {
isLoading: false
},
dtmVersion: "",
globalError: "",
}),
getters: {
getMenubar(): IMenubar {
@ -25,8 +27,16 @@ export const useLayoutStore = defineStore({
setRoutes(data: Array<IMenubarList>): void {
this.menubar.menuList = data
},
setGlobalError(err: string) {
this.globalError = err
},
concatAllowRoutes(): void {
allowRouter.reverse().forEach(v => this.menubar.menuList.unshift(v))
},
async loadDtmVersion(): Promise<void> {
const { data: { version } } = await getDtmVersion()
this.dtmVersion = version
console.log("dtm version: ", this.dtmVersion)
}
}
})

9
dashboard/src/type/index.d.ts → admin/src/type/index.d.ts

@ -1,4 +1,4 @@
export {}
export { }
declare global {
interface IObject<T> {
[index: string]: T
@ -7,10 +7,11 @@ declare global {
VITE_APP_TITLE: string
VITE_PORT: number
VITE_PROXY: string
VITE_ADMIN_VERSION: string
}
interface ITable<T = any> {
data: Array<T>
next_position: number,
size: number
data: Array<T>
next_position: number,
size: number
}
}

0
dashboard/src/type/shim.vue.d.ts → admin/src/type/shim.vue.d.ts

12
dashboard/src/type/store/layout.ts → admin/src/type/store/layout.ts

@ -1,14 +1,16 @@
export interface IMenubar {
menuList: Array<IMenubarList>
menuList: Array<IMenubarList>
}
export interface ILayout {
menubar: IMenubar
status: IStatus
menubar: IMenubar
status: IStatus
dtmVersion: string
globalError: string
}
export interface IStatus {
isLoading: boolean
isLoading: boolean
}
export interface IMenubarList {
@ -16,7 +18,7 @@ export interface IMenubarList {
id?: number | string
name: string
path: string
redirect?: string
redirect?: string
meta: {
icon?: string
title: string

1
dashboard/src/utils/request.ts → admin/src/utils/request.ts

@ -1,7 +1,6 @@
import axios from 'axios'
const request = axios.create({
baseURL: import.meta.env.VITE_APP_API_BASE_URL as string | undefined,
timeout: 60000
})

8
dashboard/src/utils/util.ts → admin/src/utils/util.ts

@ -1,9 +1,9 @@
import { useRoute } from 'vue-router';
import { IMenubarList } from '../type/store/layout';
export const findCurrentMenubar = (menuList: IMenubarList[], root?:boolean) => {
export const findCurrentMenubar = (menuList: IMenubarList[], root?: boolean) => {
const route = useRoute()
let arr:IMenubarList[] | IMenubarList = []
let arr: IMenubarList[] | IMenubarList = []
for (let i = 0; i < menuList.length; i++) {
const v = menuList[i];
const usePath = v.meta.activeMenu || v.redirect || v.path;
@ -21,3 +21,7 @@ export const findCurrentMenubar = (menuList: IMenubarList[], root?:boolean) => {
return arr
}
export const sleep = async (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms))
}

59
dashboard/src/views/Dashboard/GlobalTransactions/AllTransactions.vue → admin/src/views/Dashboard/GlobalTransactions/AllTransactions.vue

@ -4,13 +4,14 @@
<template #bodyCell="{column, record}">
<template v-if="column.key === 'status'">
<span>
<a-tag :key="record.status" :color="record.status === 'succeed' ? 'green' : 'volcano'">{{ record.status.toUpperCase() }}</a-tag>
<a-tag :key="record.status" :color="record.status === 'succeed' ? 'green' : 'volcano'">{{ record.status }}</a-tag>
</span>
</template>
<template v-else-if="column.key === 'action'">
<span>
<a class="mr-2 font-medium">Detail</a>
<a class="text-red-400 font-medium">Stop</a>
<a class="mr-2 font-medium" @click="handleTransactionDetail(record.gid)">Detail</a>
<a-button danger type="link" :disabled="record.status==='failed' || record.status==='succeed'" @click="handleTransactionStop(record.gid)">ForceStop</a-button>
<!-- <a class="text-red-400 font-medium" @click="handleTransactionStop(record.gid)">ForceStop</a> -->
</span>
</template>
</template>
@ -19,13 +20,15 @@
<a-button type="text" :disabled="!canPrev" @click="handlePrevPage">Previous</a-button>
<a-button type="text" :disabled="!canNext" @click="handleNextPage">Next</a-button>
</div>
<DialogTransactionDetail ref="transactionDetail" />
</div>
</template>
<script setup lang="ts">
import { IListAllTransactions, listAllTransactions } from '/@/api/dtm'
import { ref, onMounted, reactive, computed } from 'vue-demi'
import { IListAllTransactionsReq, listAllTransactions, forceStopTransaction } from '/@/api/api_dtm'
import { ref, computed } from 'vue-demi'
import { usePagination } from 'vue-request'
import { TableProps } from 'ant-design-vue/es/vc-table/Table'
import DialogTransactionDetail from './_Components/DialogTransactionDetail.vue';
const columns = [
{
title: 'GID',
@ -53,15 +56,11 @@ const columns = [
}
]
const pager = ref([""])
const currentState = ref(1)
const pages = ref([''])
const curPage = ref(1)
const canPrev = computed(() => {
if (currentState.value === 1) {
return false;
}
return true;
return curPage.value > 1
})
const canNext = computed(() => {
@ -79,14 +78,14 @@ type Data = {
next_position: string
}
const queryData = (params: IListAllTransactions) => {
const queryData = (params: IListAllTransactionsReq) => {
return listAllTransactions<Data>(params)
}
const { data, run, current, loading, pageSize } = usePagination(queryData, {
defaultParams: [
{
limit: 100,
limit: 10,
}
],
pagination: {
@ -97,27 +96,37 @@ const { data, run, current, loading, pageSize } = usePagination(queryData, {
const dataSource = computed(() => data.value?.data.transactions || [])
const handlePrevPage = () => {
currentState.value -= 1;
curPage.value -= 1;
const params = {
limit: 100
}
if (pager.value[currentState.value - 1]) {
params.position = pager.value[currentState.value - 1]
limit: pageSize.value,
position: pages.value[curPage.value] as string
}
run(params)
}
const handleNextPage = () => {
currentState.value += 1;
if (currentState.value >= 2) {
pager.value[currentState.value - 1] = data.value?.data.next_position
}
curPage.value += 1;
pages.value[curPage.value] = data.value?.data.next_position as string
run({
position: data.value?.data.next_position,
limit: 5
limit: pageSize.value,
})
}
const transactionDetail = ref<null | {open:(gid: string) => null}>(null)
const handleTransactionDetail = (gid: string) => {
transactionDetail.value?.open(gid)
}
const handleTransactionStop = async (gid: string) => {
await forceStopTransaction(gid)
run({
position: data.value?.data.next_position,
limit: pageSize.value,
})
}
</script>
<style lang="postcss" scoped>

0
dashboard/src/views/Dashboard/GlobalTransactions/UnfinishedTransactions.vue → admin/src/views/Dashboard/GlobalTransactions/UnfinishedTransactions.vue

117
admin/src/views/Dashboard/GlobalTransactions/_Components/DialogTransactionDetail.vue

@ -0,0 +1,117 @@
<template>
<div>
<a-modal v-model:visible="visible" title="Transaction Detail" width="100%" wrap-class-name="full-modal">
<a-table :columns="columns" :data-source="dataSource" :pagination="false">
<template #bodyCell="{column, record}">
</template>
</a-table>
<div class="mt-10 relative">
<a-textarea id="qs" v-model:value="textVal" :auto-size="{ minRows: 10, maxRows: 10 }" />
<screenfull class="absolute right-2 top-3 z-50" identity="qs" />
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getTransaction } from '/@/api/api_dtm';
import screenfull from '/@/components/Screenfull/index.vue';
// import VueJsonPretty from 'vue-json-pretty';
// import 'vue-json-pretty/lib/styles.css'
const dataSource = ref<Branches[]>([])
const visible = ref(false)
const textVal = ref('')
const open = async(gid: string) => {
const d = await getTransaction<Data>({gid: gid})
dataSource.value = d.data.branches
textVal.value = JSON.stringify(d.data, null, 2)
visible.value = true
}
const columns = [
{
title: 'GID',
dataIndex: 'gid',
key: 'gid'
}, {
title: 'BranchID',
dataIndex: 'branch_id',
key: 'branch_id'
}, {
title: 'Op',
dataIndex: 'op',
key: 'op'
}, {
title: 'Status',
dataIndex: 'status',
key: 'status'
}, {
title: 'CreateTime',
dataIndex: 'create_time',
key: 'create_time'
}, {
title: 'UpdateTime',
dataIndex: 'update_time',
key: 'update_time'
}
]
type Data = {
branches: {
gid: string
branch_id: string
op: string
status: string
create_time: string
update_time: string
}[]
transactions: {
ID: number
create_time: string
update_time: string
gid: string
trans_type: string
status: string
protocol: string
finish_time: string
options: string
next_cron_interval: number
next_cron_time: string
concurrent: boolean
}
}
interface Branches {
gid: string
branch_id: string
op: string
status: string
create_time: string
update_time: string
}
defineExpose({
open
})
</script>
<style lang="postcss">
.full-modal .ant-modal {
max-width: 100%;
top: 0;
padding-bottom: 0;
margin: 0;
}
.full-modal .ant-modal-content {
display: flex;
flex-direction: column;
height: calc(100vh);
}
.full-modal .ant-modal-body {
flex: 1;
}
</style>

0
dashboard/src/views/Dashboard/Nodes/LivingNodes.vue → admin/src/views/Dashboard/Nodes/LivingNodes.vue

0
dashboard/tailwind.config.js → admin/tailwind.config.js

0
dashboard/tsconfig.json → admin/tsconfig.json

4
dashboard/vite.config.ts → admin/vite.config.ts

@ -28,8 +28,8 @@ export default ({ }: ConfigEnv): UserConfigExport => {
})
],
server: {
port: 5000,
base: 'dashboard',
port: 6789,
base: 'admin',
proxy: {
'/api': {
target: 'http://localhost:36789',

5
dashboard/yarn.lock → admin/yarn.lock

@ -2681,6 +2681,11 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
screenfull@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/screenfull/-/screenfull-6.0.1.tgz#3b71e6f06b72d817a8d3be73c45ebe71fa8da1ce"
integrity sha512-yzQW+j4zMUBQC51xxWaoDYjxOtl8Kn+xvue3p6v/fv2pIi1jH4AldgVLU8TBfFVgH2x3VXlf3+YiA/AYIPlaew==
scroll-into-view-if-needed@^2.2.25:
version "2.2.29"
resolved "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz"

3
conf.sample.yml

@ -11,6 +11,7 @@
# User: 'root'
# Password: ''
# Port: 3306
# Db: 'dtm'
# Driver: 'boltdb' # default store engine
@ -30,8 +31,6 @@
# MaxOpenConns: 500
# MaxIdleConns: 500
# ConnMaxLifeTime: 5 # default value is 5 (minutes)
# TransGlobalTable: 'dtm.trans_global'
# TransBranchOpTable: 'dtm.trans_branch_op'
### flollowing config is only for some Driver
# DataExpire: 604800 # Trans data will expire in 7 days. only for redis/boltdb.

2
dashboard/.env.development

@ -1,2 +0,0 @@
VITE_APP_API_BASE_URL="/api"
VITE_PROXY=[["/api", "http://localhost:36789"]]

17
dashboard/src/layout/components/content.vue

@ -1,17 +0,0 @@
<template>
<router-view v-slot="{Component}">
<transition name="fade-transform" mode="out-in">
<keep-alive>
<component :is="Component" :key="key" />
</keep-alive>
</transition>
</router-view>
</template>
<script setup lang='ts'>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const key = computed(() => route.path)
</script>

13
dashboard/src/main.ts

@ -1,13 +0,0 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from '/@/router/index'
import { pinia } from '/@/store'
import '/@/permission'
import '/@/assets/css/index.css'
import 'virtual:svg-icons-register'
const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')

59
dashboard/src/router/index.ts

@ -1,59 +0,0 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { IMenubarList } from '../type/store/layout';
import { components } from './asyncRouter';
const Components: IObject<() => Promise<typeof import('*.vue')>> = Object.assign({}, components, {
LayoutHeader: (() => import('/@/layout/index.vue')) as unknown as () => Promise<typeof import('*.vue')>,
LayoutMain: (() => import('/@/layout/aside.vue')) as unknown as () => Promise<typeof import('*.vue')>
})
export const allowRouter: Array<IMenubarList> = [
{
name: 'Dashboard',
path: '/',
redirect: '/dashboard/nodes/living',
component: Components['LayoutHeader'],
meta: { title: 'Dashboard', activeMenu: '/dashboard' },
children: [
{
name: 'Nodes',
path: '/dashboard/nodes',
component: Components['LayoutMain'],
meta: { title: 'Nodes' },
children: [
{
name: 'LivingNodes',
path: '/dashboard/nodes/living',
component: Components['LivingNodes'],
meta: { title: 'Living Nodes' },
}
]
}, {
name: 'GlobalTransactions',
path: '/dashboard/global-transactions',
component: Components['LayoutMain'],
meta: { title: 'Global Transactions' },
children: [
{
name: 'AllTransactions',
path: '/dashboard/global-transactions/all',
component: Components['AllTransactions'],
meta: { title: 'All Transactions' },
}, {
name: 'UnfinishedTransactions',
path: '/dashboard/global-transactions/unfinished',
component: Components['UnfinishedTransactions'],
meta: { title: 'Unfinished Transactions' },
}
]
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes: allowRouter as RouteRecordRaw[]
})
export default router

20
dtmcli/barrier.go

@ -20,11 +20,13 @@ type BarrierBusiFunc func(tx *sql.Tx) error
// BranchBarrier every branch info
type BranchBarrier struct {
TransType string
Gid string
BranchID string
Op string
BarrierID int
TransType string
Gid string
BranchID string
Op string
BarrierID int
DBType string // DBTypeMysql | DBTypePostgres
BarrierTableName string
}
func (bb *BranchBarrier) String() string {
@ -70,8 +72,8 @@ func (bb *BranchBarrier) Call(tx *sql.Tx, busiCall BarrierBusiFunc) (rerr error)
dtmimp.OpCompensate: dtmimp.OpAction,
}[bb.Op]
originAffected, oerr := dtmimp.InsertBarrier(tx, bb.TransType, bb.Gid, bb.BranchID, originOp, bid, bb.Op)
currentAffected, rerr := dtmimp.InsertBarrier(tx, bb.TransType, bb.Gid, bb.BranchID, bb.Op, bid, bb.Op)
originAffected, oerr := dtmimp.InsertBarrier(tx, bb.TransType, bb.Gid, bb.BranchID, originOp, bid, bb.Op, bb.DBType, bb.BarrierTableName)
currentAffected, rerr := dtmimp.InsertBarrier(tx, bb.TransType, bb.Gid, bb.BranchID, bb.Op, bid, bb.Op, bb.DBType, bb.BarrierTableName)
logger.Debugf("originAffected: %d currentAffected: %d", originAffected, currentAffected)
if rerr == nil && bb.Op == dtmimp.MsgDoOp && currentAffected == 0 { // for msg's DoAndSubmit, repeated insert should be rejected.
@ -103,10 +105,12 @@ func (bb *BranchBarrier) CallWithDB(db *sql.DB, busiCall BarrierBusiFunc) error
// QueryPrepared queries prepared data
func (bb *BranchBarrier) QueryPrepared(db *sql.DB) error {
_, err := dtmimp.InsertBarrier(db, bb.TransType, bb.Gid, dtmimp.MsgDoBranch0, dtmimp.MsgDoOp, dtmimp.MsgDoBarrier1, dtmimp.OpRollback)
_, err := dtmimp.InsertBarrier(db, bb.TransType, bb.Gid, dtmimp.MsgDoBranch0, dtmimp.MsgDoOp, dtmimp.MsgDoBarrier1, dtmimp.OpRollback, bb.DBType, bb.BarrierTableName)
var reason string
if err == nil {
sql := fmt.Sprintf("select reason from %s where gid=? and branch_id=? and op=? and barrier_id=?", dtmimp.BarrierTableName)
sql = dtmimp.GetDBSpecial(bb.DBType).GetPlaceHoldSQL(sql)
logger.Debugf("queryrow: %s", sql, bb.Gid, dtmimp.MsgDoBranch0, dtmimp.MsgDoOp, dtmimp.MsgDoBarrier1)
err = db.QueryRow(sql, bb.Gid, dtmimp.MsgDoBranch0, dtmimp.MsgDoOp, dtmimp.MsgDoBarrier1).Scan(&reason)
}
if reason == dtmimp.OpRollback {

7
dtmcli/dtmimp/db_special.go

@ -75,8 +75,11 @@ func init() {
}
// GetDBSpecial get DBSpecial for currentDBType
func GetDBSpecial() DBSpecial {
return dbSpecials[currentDBType]
func GetDBSpecial(dbType string) DBSpecial {
if dbType == "" {
dbType = currentDBType
}
return dbSpecials[dbType]
}
// SetCurrentDBType set currentDBType

4
dtmcli/dtmimp/db_special_test.go

@ -18,13 +18,13 @@ func TestDBSpecial(t *testing.T) {
SetCurrentDBType("no-driver")
}))
SetCurrentDBType(DBTypeMysql)
sp := GetDBSpecial()
sp := GetDBSpecial(DBTypeMysql)
assert.Equal(t, "? ?", sp.GetPlaceHoldSQL("? ?"))
assert.Equal(t, "xa start 'xa1'", sp.GetXaSQL("start", "xa1"))
assert.Equal(t, "insert ignore into a(f) values(?)", sp.GetInsertIgnoreTemplate("a(f) values(?)", "c"))
SetCurrentDBType(DBTypePostgres)
sp = GetDBSpecial()
sp = GetDBSpecial(DBTypePostgres)
assert.Equal(t, "$1 $2", sp.GetPlaceHoldSQL("? ?"))
assert.Equal(t, "begin", sp.GetXaSQL("start", "xa1"))
assert.Equal(t, "insert into a(f) values(?) on conflict ON CONSTRAINT c do nothing", sp.GetInsertIgnoreTemplate("a(f) values(?)", "c"))

12
dtmcli/dtmimp/trans_xa_base.go

@ -18,14 +18,14 @@ func XaHandlePhase2(gid string, dbConf DBConf, branchID string, op string) error
return err
}
xaID := gid + "-" + branchID
_, err = DBExec(db, GetDBSpecial().GetXaSQL(op, xaID))
_, err = DBExec(dbConf.Driver, db, GetDBSpecial(dbConf.Driver).GetXaSQL(op, xaID))
if err != nil &&
(strings.Contains(err.Error(), "XAER_NOTA") || strings.Contains(err.Error(), "does not exist")) { // Repeat commit/rollback with the same id, report this error, ignore
err = nil
}
if op == OpRollback && err == nil {
// rollback insert a row after prepare. no-error means prepare has finished.
_, err = InsertBarrier(db, "xa", gid, branchID, OpAction, XaBarrier1, op)
_, err = InsertBarrier(db, "xa", gid, branchID, OpAction, XaBarrier1, op, dbConf.Driver, "")
}
return err
}
@ -39,20 +39,20 @@ func XaHandleLocalTrans(xa *TransBase, dbConf DBConf, cb func(*sql.DB) error) (r
}
defer func() { _ = db.Close() }()
defer DeferDo(&rerr, func() error {
_, err := DBExec(db, GetDBSpecial().GetXaSQL("prepare", xaBranch))
_, err := DBExec(dbConf.Driver, db, GetDBSpecial(dbConf.Driver).GetXaSQL("prepare", xaBranch))
return err
}, func() error {
return nil
})
_, rerr = DBExec(db, GetDBSpecial().GetXaSQL("start", xaBranch))
_, rerr = DBExec(dbConf.Driver, db, GetDBSpecial(dbConf.Driver).GetXaSQL("start", xaBranch))
if rerr != nil {
return
}
defer func() {
_, _ = DBExec(db, GetDBSpecial().GetXaSQL("end", xaBranch))
_, _ = DBExec(dbConf.Driver, db, GetDBSpecial(dbConf.Driver).GetXaSQL("end", xaBranch))
}()
// prepare and rollback both insert a row
_, rerr = InsertBarrier(db, xa.TransType, xa.Gid, xa.BranchID, OpAction, XaBarrier1, OpAction)
_, rerr = InsertBarrier(db, xa.TransType, xa.Gid, xa.BranchID, OpAction, XaBarrier1, OpAction, dbConf.Driver, "")
if rerr == nil {
rerr = cb(db)
}

16
dtmcli/dtmimp/utils.go

@ -187,12 +187,12 @@ func XaDB(conf DBConf) (*sql.DB, error) {
}
// DBExec use raw db to exec
func DBExec(db DB, sql string, values ...interface{}) (affected int64, rerr error) {
func DBExec(dbType string, db DB, sql string, values ...interface{}) (affected int64, rerr error) {
if sql == "" {
return 0, nil
}
began := time.Now()
sql = GetDBSpecial().GetPlaceHoldSQL(sql)
sql = GetDBSpecial(dbType).GetPlaceHoldSQL(sql)
r, rerr := db.Exec(sql, values...)
used := time.Since(began) / time.Millisecond
if rerr == nil {
@ -262,10 +262,16 @@ func EscapeGet(qs url.Values, key string) string {
}
// InsertBarrier insert a record to barrier
func InsertBarrier(tx DB, transType string, gid string, branchID string, op string, barrierID string, reason string) (int64, error) {
func InsertBarrier(tx DB, transType string, gid string, branchID string, op string, barrierID string, reason string, dbType string, barrierTableName string) (int64, error) {
if op == "" {
return 0, nil
}
sql := GetDBSpecial().GetInsertIgnoreTemplate(BarrierTableName+"(trans_type, gid, branch_id, op, barrier_id, reason) values(?,?,?,?,?,?)", "uniq_barrier")
return DBExec(tx, sql, transType, gid, branchID, op, barrierID, reason)
if dbType == "" {
dbType = currentDBType
}
if barrierTableName == "" {
barrierTableName = BarrierTableName
}
sql := GetDBSpecial(dbType).GetInsertIgnoreTemplate(barrierTableName+"(trans_type, gid, branch_id, op, barrier_id, reason) values(?,?,?,?,?,?)", "uniq_barrier")
return DBExec(dbType, tx, sql, transType, gid, branchID, op, barrierID, reason)
}

4
dtmgrpc/type.go

@ -8,6 +8,7 @@ package dtmgrpc
import (
context "context"
"fmt"
"github.com/dtm-labs/dtm/dtmcli"
"github.com/dtm-labs/dtm/dtmcli/dtmimp"
@ -38,7 +39,8 @@ func GrpcError2DtmError(err error) error {
if st.Message() == dtmcli.ResultOngoing {
return dtmcli.ErrOngoing
}
return dtmcli.ErrFailure
return fmt.Errorf("%s. %w", st.Message(), dtmcli.ErrFailure)
} else if ok && st.Code() == codes.FailedPrecondition {
return dtmcli.ErrOngoing
}

3
dtmsvr/api.go

@ -15,6 +15,9 @@ import (
"github.com/dtm-labs/dtm/dtmsvr/storage"
)
// Version store the passin version for dtm server
var Version = ""
func svcSubmit(t *TransGlobal) interface{} {
t.Status = dtmcli.StatusSubmitted
branches, err := t.saveNew()

6
dtmsvr/api_http.go

@ -19,6 +19,9 @@ import (
)
func addRoute(engine *gin.Engine) {
engine.GET("/api/dtmsvr/version", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
return gin.H{"version": Version}
}))
engine.GET("/api/dtmsvr/newGid", dtmutil.WrapHandler2(newGid))
engine.POST("/api/dtmsvr/prepare", dtmutil.WrapHandler2(prepare))
engine.POST("/api/dtmsvr/submit", dtmutil.WrapHandler2(submit))
@ -89,8 +92,7 @@ func all(c *gin.Context) interface{} {
return map[string]interface{}{"transactions": globals, "next_position": position}
}
// resetCronTime rest nextCronTime
// Prevent multiple backoff from causing NextCronTime to be too long
// unfinished transactions need to be retried as soon as possible after business downtime is recovered
func resetCronTime(c *gin.Context) interface{} {
sTimeoutSecond := dtmimp.OrString(c.Query("timeout"), strconv.FormatInt(3*conf.TimeoutToFail, 10))
sLimit := dtmimp.OrString(c.Query("limit"), "100")

13
dtmsvr/config/config.go

@ -6,7 +6,7 @@ import (
"github.com/dtm-labs/dtm/dtmcli"
"github.com/dtm-labs/dtm/dtmcli/logger"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
const (
@ -53,14 +53,13 @@ type Store struct {
Port int64 `yaml:"Port"`
User string `yaml:"User"`
Password string `yaml:"Password"`
Db string `yaml:"Db" default:"dtm"`
MaxOpenConns int64 `yaml:"MaxOpenConns" default:"500"`
MaxIdleConns int64 `yaml:"MaxIdleConns" default:"500"`
ConnMaxLifeTime int64 `yaml:"ConnMaxLifeTime" default:"5"`
DataExpire int64 `yaml:"DataExpire" default:"604800"` // Trans data will expire in 7 days. only for redis/boltdb.
FinishedDataExpire int64 `yaml:"FinishedDataExpire" default:"86400"` // finished Trans data will expire in 1 days. only for redis.
RedisPrefix string `yaml:"RedisPrefix" default:"{a}"` // Redis storage prefix. store data to only one slot in cluster
TransGlobalTable string `yaml:"TransGlobalTable" default:"dtm.trans_global"`
TransBranchOpTable string `yaml:"TransBranchOpTable" default:"dtm.trans_branch_op"`
}
// IsDB checks config driver is mysql or postgres
@ -76,10 +75,12 @@ func (s *Store) GetDBConf() dtmcli.DBConf {
Port: s.Port,
User: s.User,
Password: s.Password,
Db: s.Db,
}
}
type configType struct {
// Type is the type for the config of dtm server
type Type struct {
Store Store `yaml:"Store"`
TransCronInterval int64 `yaml:"TransCronInterval" default:"3"`
TimeoutToFail int64 `yaml:"TimeoutToFail" default:"35"`
@ -97,7 +98,7 @@ type configType struct {
}
// Config config
var Config = configType{}
var Config = Type{}
// MustLoadConfig load config from env and file
func MustLoadConfig(confFile string) {
@ -105,7 +106,7 @@ func MustLoadConfig(confFile string) {
if confFile != "" {
cont, err := ioutil.ReadFile(confFile)
logger.FatalIfError(err)
err = yaml.UnmarshalStrict(cont, &Config)
err = yaml.Unmarshal(cont, &Config)
logger.FatalIfError(err)
}
scont, err := json.MarshalIndent(&Config, "", " ")

2
dtmsvr/config/config_utils.go

@ -58,7 +58,7 @@ func toUnderscoreUpper(key string) string {
return strings.ToUpper(s2)
}
func checkConfig(conf *configType) error {
func checkConfig(conf *Type) error {
if conf.RetryInterval < 10 {
return errors.New("RetryInterval should not be less than 10")
}

21
dtmsvr/entry/main.go

@ -14,13 +14,6 @@ import (
"go.uber.org/automaxprocs/maxprocs"
)
func ver(version *string) {
if *version == "" {
*version = "0.0.0-dev"
}
fmt.Printf("dtm version: %s\n", *version)
}
func usage() {
cmd := filepath.Base(os.Args[0])
s := "Usage: %s [options]\n\n"
@ -35,14 +28,18 @@ var isReset = flag.Bool("r", false, "Reset dtm server data.")
var confFile = flag.String("c", "", "Path to the server configuration file.")
// Main is the entry point of dtm server.
func Main(version *string) *gin.Engine {
func Main(version *string) (*gin.Engine, *config.Type) {
flag.Parse()
if *version == "" {
*version = "v0.0.0-dev"
}
dtmsvr.Version = *version
if flag.NArg() > 0 || *isHelp {
usage()
return nil
return nil, nil
} else if *isVersion {
ver(version)
return nil
fmt.Printf("dtm version: %s\n", *version)
return nil, nil
}
logger.Infof("dtm version is: %s", *version)
config.MustLoadConfig(*confFile)
@ -58,5 +55,5 @@ func Main(version *string) *gin.Engine {
registry.WaitStoreUp()
app := dtmsvr.StartSvr() // start dtmsvr api
go dtmsvr.CronExpiredTrans(-1) // start dtmsvr cron job
return app
return app, &config.Config
}

41
dtmsvr/storage/boltdb/boltdb.go

@ -11,7 +11,6 @@ import (
"strings"
"time"
"github.com/dtm-labs/dtm/dtmcli"
"github.com/dtm-labs/dtm/dtmcli/dtmimp"
"github.com/dtm-labs/dtm/dtmcli/logger"
"github.com/dtm-labs/dtm/dtmsvr/storage"
@ -276,7 +275,7 @@ func (s *Store) ScanTransGlobalStores(position *string, limit int64) []storage.T
globals := []storage.TransGlobalStore{}
err := s.boltDb.View(func(t *bolt.Tx) error {
cursor := t.Bucket(bucketGlobal).Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
for k, v := cursor.Seek([]byte(*position)); k != nil; k, v = cursor.Next() {
if string(k) == *position {
continue
}
@ -383,42 +382,42 @@ func (s *Store) TouchCronTime(global *storage.TransGlobalStore, nextCronInterval
// LockOneGlobalTrans finds GlobalTrans
func (s *Store) LockOneGlobalTrans(expireIn time.Duration) *storage.TransGlobalStore {
var trans *storage.TransGlobalStore
var transo *storage.TransGlobalStore
min := fmt.Sprintf("%d", time.Now().Add(expireIn).Unix())
next := time.Now().Add(time.Duration(s.retryInterval) * time.Second)
err := s.boltDb.Update(func(t *bolt.Tx) error {
cursor := t.Bucket(bucketIndex).Cursor()
toDelete := [][]byte{}
for trans == nil || trans.Status == dtmcli.StatusSucceed || trans.Status == dtmcli.StatusFailed {
k, v := cursor.First()
if k == nil || string(k) > min {
return storage.ErrNotFound
}
trans = tGetGlobal(t, string(v))
for k, v := cursor.First(); k != nil && string(k) <= min; k, v = cursor.Next() {
toDelete = append(toDelete, k)
trans := tGetGlobal(t, string(v))
if trans != nil && !trans.IsFinished() {
transo = trans
break
}
}
for _, k := range toDelete {
err := t.Bucket(bucketIndex).Delete(k)
dtmimp.E2P(err)
}
trans.NextCronTime = &next
tPutGlobal(t, trans)
tPutIndex(t, next.Unix(), trans.Gid)
if transo != nil {
next := time.Now().Add(time.Duration(s.retryInterval) * time.Second)
transo.NextCronTime = &next
tPutGlobal(t, transo)
tPutIndex(t, next.Unix(), transo.Gid)
}
return nil
})
if err == storage.ErrNotFound {
return nil
}
dtmimp.E2P(err)
return trans
return transo
}
// ResetCronTime rest nextCronTime
// Prevent multiple backoff from causing NextCronTime to be too long
func (s *Store) ResetCronTime(timeout time.Duration, limit int64) (succeedCount int64, hasRemaining bool, err error) {
// ResetCronTime reset nextCronTime
// unfinished transactions need to be retried as soon as possible after business downtime is recovered
func (s *Store) ResetCronTime(after time.Duration, limit int64) (succeedCount int64, hasRemaining bool, err error) {
next := time.Now()
var trans *storage.TransGlobalStore
min := fmt.Sprintf("%d", time.Now().Add(timeout).Unix())
min := fmt.Sprintf("%d", time.Now().Add(after).Unix())
err = s.boltDb.Update(func(t *bolt.Tx) error {
cursor := t.Bucket(bucketIndex).Cursor()
succeedCount = 0

8
dtmsvr/storage/redis/redis.go

@ -270,11 +270,11 @@ return gid
}
}
// ResetCronTime rest nextCronTime
// Prevent multiple backoff from causing NextCronTime to be too long
func (s *Store) ResetCronTime(timeout time.Duration, limit int64) (succeedCount int64, hasRemaining bool, err error) {
// ResetCronTime reset nextCronTime
// unfinished transactions need to be retried as soon as possible after business downtime is recovered
func (s *Store) ResetCronTime(after time.Duration, limit int64) (succeedCount int64, hasRemaining bool, err error) {
next := time.Now().Unix()
timeoutTimestamp := time.Now().Add(timeout).Unix()
timeoutTimestamp := time.Now().Add(after).Unix()
args := newArgList().AppendGid("").AppendRaw(timeoutTimestamp).AppendRaw(next).AppendRaw(limit)
lua := `-- ResetCronTime
local r = redis.call('ZRANGEBYSCORE', KEYS[3], ARGV[3], '+inf', 'LIMIT', 0, ARGV[5]+1)

2
dtmsvr/storage/registry/registry.go

@ -49,7 +49,7 @@ func GetStore() storage.Store {
// WaitStoreUp wait for db to go up
func WaitStoreUp() {
for err := GetStore().Ping(); err != nil; err = GetStore().Ping() {
logger.Infof("wait store up")
logger.Infof("wait store up: %v", err)
time.Sleep(3 * time.Second)
}
}

81
dtmsvr/storage/sql/sql.go

@ -137,61 +137,44 @@ func (s *Store) TouchCronTime(global *storage.TransGlobalStore, nextCronInterval
// LockOneGlobalTrans finds GlobalTrans
func (s *Store) LockOneGlobalTrans(expireIn time.Duration) *storage.TransGlobalStore {
db := dbGet()
getTime := func(second int) string {
return map[string]string{
"mysql": fmt.Sprintf("date_add(now(), interval %d second)", second),
"postgres": fmt.Sprintf("current_timestamp + interval '%d second'", second),
}[conf.Store.Driver]
}
expire := int(expireIn / time.Second)
whereTime := fmt.Sprintf("next_cron_time < %s", getTime(expire))
owner := shortuuid.New()
global := &storage.TransGlobalStore{}
dbr := db.Must().Model(global).
Where(whereTime + "and status in ('prepared', 'aborting', 'submitted')").
Limit(1).
Select([]string{"owner", "next_cron_time"}).
Updates(&storage.TransGlobalStore{
Owner: owner,
NextCronTime: dtmutil.GetNextTime(conf.RetryInterval),
})
if dbr.RowsAffected == 0 {
nextCronTime := getTimeStr(int64(expireIn / time.Second))
where := map[string]string{
dtmimp.DBTypeMysql: fmt.Sprintf(`next_cron_time < '%s' and status in ('prepared', 'aborting', 'submitted') limit 1`, nextCronTime),
dtmimp.DBTypePostgres: fmt.Sprintf(`id in (select id from trans_global where next_cron_time < '%s' and status in ('prepared', 'aborting', 'submitted') limit 1 )`, nextCronTime),
}[conf.Store.Driver]
sql := fmt.Sprintf(`UPDATE trans_global SET update_time='%s',next_cron_time='%s', owner='%s' WHERE %s`,
getTimeStr(0),
getTimeStr(conf.RetryInterval),
owner,
where)
affected, err := dtmimp.DBExec(conf.Store.Driver, db.ToSQLDB(), sql)
dtmimp.PanicIf(err != nil, err)
if affected == 0 {
return nil
}
global := &storage.TransGlobalStore{}
db.Must().Where("owner=?", owner).First(global)
return global
}
// ResetCronTime rest nextCronTime
// Prevent multiple backoff from causing NextCronTime to be too long
func (s *Store) ResetCronTime(timeout time.Duration, limit int64) (succeedCount int64, hasRemaining bool, err error) {
db := dbGet()
getTime := func(second int) string {
return map[string]string{
"mysql": fmt.Sprintf("date_add(now(), interval %d second)", second),
"postgres": fmt.Sprintf("current_timestamp + interval '%d second'", second),
}[conf.Store.Driver]
}
timeoutSecond := int(timeout / time.Second)
whereTime := fmt.Sprintf("next_cron_time > %s", getTime(timeoutSecond))
global := &storage.TransGlobalStore{}
dbr := db.Must().Model(global).
Where(whereTime + "and status in ('prepared', 'aborting', 'submitted')").
Limit(int(limit)).
Select([]string{"next_cron_time"}).
Updates(&storage.TransGlobalStore{
NextCronTime: dtmutil.GetNextTime(0),
})
succeedCount = dbr.RowsAffected
if succeedCount == limit {
var count int64
db.Must().Model(global).Where(whereTime + "and status in ('prepared', 'aborting', 'submitted')").Limit(1).Count(&count)
if count > 0 {
hasRemaining = true
}
}
// ResetCronTime reset nextCronTime
// unfinished transactions need to be retried as soon as possible after business downtime is recovered
func (s *Store) ResetCronTime(after time.Duration, limit int64) (succeedCount int64, hasRemaining bool, err error) {
nextCronTime := getTimeStr(int64(after / time.Second))
where := map[string]string{
dtmimp.DBTypeMysql: fmt.Sprintf(`next_cron_time > '%s' and status in ('prepared', 'aborting', 'submitted') limit %d`, nextCronTime, limit),
dtmimp.DBTypePostgres: fmt.Sprintf(`id in (select id from trans_global where next_cron_time > '%s' and status in ('prepared', 'aborting', 'submitted') limit %d )`, nextCronTime, limit),
}[conf.Store.Driver]
return succeedCount, hasRemaining, dbr.Error
sql := fmt.Sprintf(`UPDATE trans_global SET update_time='%s',next_cron_time='%s' WHERE %s`,
getTimeStr(0),
getTimeStr(0),
where)
affected, err := dtmimp.DBExec(conf.Store.Driver, dbGet().ToSQLDB(), sql)
return affected, affected == limit, err
}
// SetDBConn sets db conn pool
@ -213,3 +196,7 @@ func wrapError(err error) error {
dtmimp.E2P(err)
return err
}
func getTimeStr(afterSecond int64) string {
return dtmutil.GetNextTime(afterSecond).Format("2006-01-02 15:04:05")
}

2
dtmsvr/storage/store.go

@ -30,5 +30,5 @@ type Store interface {
ChangeGlobalStatus(global *TransGlobalStore, newStatus string, updates []string, finished bool)
TouchCronTime(global *TransGlobalStore, nextCronInterval int64, nextCronTime *time.Time)
LockOneGlobalTrans(expireIn time.Duration) *TransGlobalStore
ResetCronTime(timeout time.Duration, limit int64) (succeedCount int64, hasRemaining bool, err error)
ResetCronTime(after time.Duration, limit int64) (succeedCount int64, hasRemaining bool, err error)
}

12
dtmsvr/storage/trans.go

@ -11,7 +11,6 @@ import (
"github.com/dtm-labs/dtm/dtmcli"
"github.com/dtm-labs/dtm/dtmcli/dtmimp"
"github.com/dtm-labs/dtm/dtmsvr/config"
"github.com/dtm-labs/dtm/dtmutil"
)
@ -33,6 +32,7 @@ type TransGlobalStore struct {
Protocol string `json:"protocol,omitempty"`
FinishTime *time.Time `json:"finish_time,omitempty"`
RollbackTime *time.Time `json:"rollback_time,omitempty"`
RollbackReason string `json:"rollback_reason,omitempty"`
Options string `json:"options,omitempty"`
CustomData string `json:"custom_data,omitempty"`
NextCronInterval int64 `json:"next_cron_interval,omitempty"`
@ -45,13 +45,18 @@ type TransGlobalStore struct {
// TableName TableName
func (g *TransGlobalStore) TableName() string {
return config.Config.Store.TransGlobalTable
return "trans_global"
}
func (g *TransGlobalStore) String() string {
return dtmimp.MustMarshalString(g)
}
// IsFinished return true if status == "failed" || status == "succeed"
func (g *TransGlobalStore) IsFinished() bool {
return g.Status == dtmcli.StatusFailed || g.Status == dtmcli.StatusSucceed
}
// TransBranchStore branch transaction
type TransBranchStore struct {
dtmutil.ModelBase
@ -63,11 +68,12 @@ type TransBranchStore struct {
Status string `json:"status,omitempty"`
FinishTime *time.Time `json:"finish_time,omitempty"`
RollbackTime *time.Time `json:"rollback_time,omitempty"`
Error error `json:"-" gorm:"-"`
}
// TableName TableName
func (b *TransBranchStore) TableName() string {
return config.Config.Store.TransBranchOpTable
return "trans_branch_op"
}
func (b *TransBranchStore) String() string {

22
dtmsvr/trans_status.go

@ -36,7 +36,22 @@ func (t *TransGlobal) touchCronTime(ctype cronType, delay uint64) {
logger.Infof("TouchCronTime for: %s", t.TransGlobalStore.String())
}
func (t *TransGlobal) changeStatus(status string) {
type changeStatusParams struct {
rollbackReason string
}
type changeStatusOption func(c *changeStatusParams)
func withRollbackReason(rollbackReason string) changeStatusOption {
return func(c *changeStatusParams) {
c.rollbackReason = rollbackReason
}
}
func (t *TransGlobal) changeStatus(status string, opts ...changeStatusOption) {
statusParams := &changeStatusParams{}
for _, opt := range opts {
opt(statusParams)
}
updates := []string{"status", "update_time"}
now := time.Now()
if status == dtmcli.StatusSucceed {
@ -46,6 +61,10 @@ func (t *TransGlobal) changeStatus(status string) {
t.RollbackTime = &now
updates = append(updates, "rollback_time")
}
if statusParams.rollbackReason != "" {
t.RollbackReason = statusParams.rollbackReason
updates = append(updates, "rollback_reason")
}
t.UpdateTime = &now
GetStore().ChangeGlobalStatus(&t.TransGlobalStore, status, updates, status == dtmcli.StatusSucceed || status == dtmcli.StatusFailed)
logger.Infof("ChangeGlobalStatus to %s ok for %s", status, t.TransGlobalStore.String())
@ -171,6 +190,7 @@ func (t *TransGlobal) getBranchResult(branch *TransBranch) (string, error) {
if err == nil {
return dtmcli.StatusSucceed, nil
} else if t.TransType == "saga" && branch.Op == dtmimp.OpAction && errors.Is(err, dtmcli.ErrFailure) {
branch.Error = fmt.Errorf("url:%s return failed: %w", branch.URL, err)
return dtmcli.StatusFailed, nil
} else if errors.Is(err, dtmcli.ErrOngoing) {
return "", dtmcli.ErrOngoing

16
dtmsvr/trans_type_saga.go

@ -49,13 +49,14 @@ type branchResult struct {
status string
started bool
op string
err error
}
func (t *transSagaProcessor) ProcessOnce(branches []TransBranch) error {
// when saga tasks is fetched, it always need to process
logger.Debugf("status: %s timeout: %t", t.Status, t.isTimeout())
if t.Status == dtmcli.StatusSubmitted && t.isTimeout() {
t.changeStatus(dtmcli.StatusAborting)
t.changeStatus(dtmcli.StatusAborting, withRollbackReason(fmt.Sprintf("Timeout after %d seconds", t.TimeoutToFail)))
}
n := len(branches)
@ -73,6 +74,7 @@ func (t *transSagaProcessor) ProcessOnce(branches []TransBranch) error {
}
// resultStats
var rsAToStart, rsAStarted, rsADone, rsAFailed, rsASucceed, rsCToStart, rsCDone, rsCSucceed int
var failureError error
branchResults := make([]branchResult, n) // save the branch result
for i := 0; i < n; i++ {
b := branches[i]
@ -125,9 +127,9 @@ func (t *transSagaProcessor) ProcessOnce(branches []TransBranch) error {
if x := recover(); x != nil {
err = dtmimp.AsError(x)
}
resultChan <- branchResult{index: i, status: branches[i].Status, op: branches[i].Op}
resultChan <- branchResult{index: i, status: branches[i].Status, op: branches[i].Op, err: branches[i].Error}
if err != nil && !errors.Is(err, dtmcli.ErrOngoing) {
logger.Errorf("exec branch error: %v", err)
logger.Errorf("exec branch %s %s %s error: %v", branches[i].BranchID, branches[i].Op, branches[i].URL, err)
}
}()
err = t.execBranch(&branches[i], i)
@ -172,6 +174,7 @@ func (t *transSagaProcessor) ProcessOnce(branches []TransBranch) error {
rsADone++
if r.status == dtmcli.StatusFailed {
rsAFailed++
failureError = r.err
} else if r.status == dtmcli.StatusSucceed {
rsASucceed++
}
@ -219,8 +222,11 @@ func (t *transSagaProcessor) ProcessOnce(branches []TransBranch) error {
t.changeStatus(dtmcli.StatusSucceed)
return nil
}
if t.Status == dtmcli.StatusSubmitted && (rsAFailed > 0 || t.isTimeout()) {
t.changeStatus(dtmcli.StatusAborting)
if t.Status == dtmcli.StatusSubmitted && rsAFailed > 0 {
t.changeStatus(dtmcli.StatusAborting, withRollbackReason(failureError.Error()))
}
if t.Status == dtmcli.StatusSubmitted && t.isTimeout() {
t.changeStatus(dtmcli.StatusAborting, withRollbackReason(fmt.Sprintf("Timeout after %d seconds", t.TimeoutToFail)))
}
if t.Status == dtmcli.StatusAborting {
prepareToCompensate()

4
dtmsvr/trans_type_tcc.go

@ -1,6 +1,8 @@
package dtmsvr
import (
"fmt"
"github.com/dtm-labs/dtm/dtmcli"
"github.com/dtm-labs/dtm/dtmcli/dtmimp"
"github.com/dtm-labs/dtm/dtmcli/logger"
@ -23,7 +25,7 @@ func (t *transTccProcessor) ProcessOnce(branches []TransBranch) error {
return nil
}
if t.Status == dtmcli.StatusPrepared && t.isTimeout() {
t.changeStatus(dtmcli.StatusAborting)
t.changeStatus(dtmcli.StatusAborting, withRollbackReason(fmt.Sprintf("Timeout after %d seconds", t.TimeoutToFail)))
}
op := dtmimp.If(t.Status == dtmcli.StatusSubmitted, dtmimp.OpConfirm, dtmimp.OpCancel).(string)
for current := len(branches) - 1; current >= 0; current-- {

4
dtmsvr/trans_type_xa.go

@ -1,6 +1,8 @@
package dtmsvr
import (
"fmt"
"github.com/dtm-labs/dtm/dtmcli"
"github.com/dtm-labs/dtm/dtmcli/dtmimp"
)
@ -22,7 +24,7 @@ func (t *transXaProcessor) ProcessOnce(branches []TransBranch) error {
return nil
}
if t.Status == dtmcli.StatusPrepared && t.isTimeout() {
t.changeStatus(dtmcli.StatusAborting)
t.changeStatus(dtmcli.StatusAborting, withRollbackReason(fmt.Sprintf("Timeout after %d seconds", t.TimeoutToFail)))
}
currentType := dtmimp.If(t.Status == dtmcli.StatusSubmitted, dtmimp.OpCommit, dtmimp.OpRollback).(string)
for i, branch := range branches {

2
dtmutil/db.go

@ -100,7 +100,7 @@ func DbGet(conf dtmcli.DBConf, ops ...func(*gorm.DB)) *DB {
dsn := dtmimp.GetDsn(conf)
db, ok := dbs.Load(dsn)
if !ok {
logger.Infof("connecting '%s' '%s' '%s' '%d'", conf.Driver, conf.Host, conf.User, conf.Port)
logger.Infof("connecting '%s' '%s' '%s' '%d' '%s'", conf.Driver, conf.Host, conf.User, conf.Port, conf.Db)
db1, err := gorm.Open(getGormDialetor(conf.Driver, dsn), &gorm.Config{
SkipDefaultTransaction: true,
})

4
dtmutil/utils.go

@ -64,7 +64,7 @@ func WrapHandler(fn func(*gin.Context) interface{}) gin.HandlerFunc {
}
}
// WrapHandler2 wrap a function te bo the handler of gin request
// WrapHandler2 wrap a function to be the handler of gin request
// used by dtmsvr
func WrapHandler2(fn func(*gin.Context) interface{}) gin.HandlerFunc {
return func(c *gin.Context) {
@ -168,7 +168,7 @@ func RunSQLScript(conf dtmcli.DBConf, script string, skipDrop bool) {
if s == "" || (skipDrop && strings.Contains(s, "drop")) {
continue
}
_, err = dtmimp.DBExec(con, s)
_, err = dtmimp.DBExec(conf.Driver, con, s)
logger.FatalIfError(err)
logger.Infof("sql scripts finished: %s", s)
}

13
go.mod

@ -6,12 +6,13 @@ require (
bou.ke/monkey v1.0.2
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/dtm-labs/dtmdriver v0.0.3
github.com/dtm-labs/dtmdriver-gozero v0.0.4
github.com/dtm-labs/dtmdriver-gozero v0.0.5
github.com/dtm-labs/dtmdriver-http v1.2.2
github.com/dtm-labs/dtmdriver-kratos v0.0.8
github.com/dtm-labs/dtmdriver-polaris v0.0.4
github.com/dtm-labs/dtmdriver-protocol1 v0.0.1
github.com/gin-gonic/gin v1.7.7
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/go-redis/redis/v8 v8.11.4
github.com/go-resty/resty/v2 v2.7.0
github.com/go-sql-driver/mysql v1.6.0
@ -22,14 +23,16 @@ require (
github.com/onsi/gomega v1.16.0
github.com/prometheus/client_golang v1.11.0
github.com/stretchr/testify v1.7.1
github.com/ugorji/go v1.2.7 // indirect
go.etcd.io/bbolt v1.3.6
go.mongodb.org/mongo-driver v1.8.3
go.mongodb.org/mongo-driver v1.9.0
go.uber.org/automaxprocs v1.5.1
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
google.golang.org/grpc v1.45.0
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
google.golang.org/grpc v1.46.2
google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.0.3
gorm.io/driver/postgres v1.2.1
gorm.io/gorm v1.22.2

100
go.sum

@ -67,8 +67,10 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.17.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1402/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1620 h1:diBUSNskvZjiX1LFa3I+AfHaVnKvphXHddf0eVuCVFg=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1620/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@ -85,6 +87,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -106,6 +109,7 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
@ -131,10 +135,8 @@ github.com/dtm-labs/dtmdriver v0.0.1/go.mod h1:fLiEeD2BPwM9Yq96TfcP9KpbTwFsn5nTx
github.com/dtm-labs/dtmdriver v0.0.2/go.mod h1:fLiEeD2BPwM9Yq96TfcP9KpbTwFsn5nTxa/PP0jmFuk=
github.com/dtm-labs/dtmdriver v0.0.3 h1:9iAtvXKR3lJXQ7dvS87e4xdtmqkzN+ofek+CF9AvUSY=
github.com/dtm-labs/dtmdriver v0.0.3/go.mod h1:fLiEeD2BPwM9Yq96TfcP9KpbTwFsn5nTxa/PP0jmFuk=
github.com/dtm-labs/dtmdriver-gozero v0.0.3 h1:KXaEFYzykSEhiLB2+baV8X1PaHLKh85bpJISK8+H86E=
github.com/dtm-labs/dtmdriver-gozero v0.0.3/go.mod h1:B5FqzfvzGe2AcB8MpAP34eSLszkCd6hm+t5KJN2Fu/o=
github.com/dtm-labs/dtmdriver-gozero v0.0.4 h1:GN+x3pmk545BgvbR4oiKhJ7N8k5yZ2rkG1aCwj8ZDZ8=
github.com/dtm-labs/dtmdriver-gozero v0.0.4/go.mod h1:B5FqzfvzGe2AcB8MpAP34eSLszkCd6hm+t5KJN2Fu/o=
github.com/dtm-labs/dtmdriver-gozero v0.0.5 h1:cMBkURDRPcb1Y6/kY+x/yxYA8KWMG+fkK6b+cs4WXKo=
github.com/dtm-labs/dtmdriver-gozero v0.0.5/go.mod h1:U4nIisQ5SBZrajp7ky1Bi0eBxPiGqVy+tOs47beobY4=
github.com/dtm-labs/dtmdriver-http v1.2.2 h1:QOul+PpK1KQyXXx5viNrHrEFIc/nFxmX4fJfI3DLUqI=
github.com/dtm-labs/dtmdriver-http v1.2.2/go.mod h1:UtWShS61TiiudZUAabQ2ww0CzSEpBYF3AS3F3G2Jc2o=
github.com/dtm-labs/dtmdriver-kratos v0.0.8 h1:AAIfFzpzuu7K3B/wpx71y0FdZqO1X/Bsklva0bayS9s=
@ -156,8 +158,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
@ -178,8 +182,10 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.4.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -218,12 +224,15 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
@ -444,14 +453,19 @@ github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/U
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -487,8 +501,12 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE=
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8=
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -551,6 +569,7 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nacos-group/nacos-sdk-go v1.0.9/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA=
github.com/nacos-group/nacos-sdk-go v1.1.1 h1:beczWcOoTaVBMgCgikqvZflrN5Xbw7pWAWpxl+VJGIA=
github.com/nacos-group/nacos-sdk-go v1.1.1/go.mod h1:UHOtQNQY/qpk2dhg6gDq8u5+/CEIc3+lWmrmxEzX0/g=
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
@ -626,6 +645,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@ -673,15 +693,19 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@ -700,10 +724,13 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/zeromicro/go-zero v1.3.0/go.mod h1:Hy4o1VFAt32lXaQMbaBhoFeZjA/rJqJ4PTGNdGsURcc=
github.com/zeromicro/go-zero v1.3.2 h1:2HcmceZDEGwZWvofCG+0GXyh+Gtz/wKCW4Fq8Mb7KIg=
github.com/zeromicro/go-zero v1.3.2/go.mod h1:DEj3Fwj1Ui1ltsgf6YqwTL9nD4+tYzIRX0c1pWtQo1E=
github.com/zeromicro/go-zero v1.3.3 h1:6qv9PcfqfB1tMgp1FJlP1LfWSZ4XD+FwojvA2h5LL2k=
github.com/zeromicro/go-zero v1.3.3/go.mod h1:LwuYc2V04ZHhWPWGJYQ+kJ5DT4QSkeaZGqXiQcpkfks=
github.com/zeromicro/zero-contrib/zrpc/registry/consul v0.0.0-20220228111653-d672d81f39ab h1:cgWxVhYV2Mr4ZClJTecxt0V5u5jvrayLzFYlUzoG5qI=
github.com/zeromicro/zero-contrib/zrpc/registry/consul v0.0.0-20220228111653-d672d81f39ab/go.mod h1:kM7gqMjv0B7QI1UsmJhN5UVWxQao9Oe3Jt3JnXc9o+c=
github.com/zeromicro/zero-contrib/zrpc/registry/nacos v0.0.0-20220525162615-f10f16d580d6 h1:2+DgXySLxSsB0LoYgk+AinZKnWq2mc5lxbqUQ7qTcvY=
github.com/zeromicro/zero-contrib/zrpc/registry/nacos v0.0.0-20220525162615-f10f16d580d6/go.mod h1:5yKg0EdtswP7sn1snNAkZ/6aj/zBYcyWOqTpLSAtW3M=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
@ -718,23 +745,23 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL
go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA=
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4=
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
go.mongodb.org/mongo-driver v1.9.0 h1:f3aLGJvQmBl8d9S40IL+jEyBC6hfLPbJjv9t5hEM9ck=
go.mongodb.org/mongo-driver v1.9.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE=
go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc=
go.opentelemetry.io/otel/exporters/zipkin v1.3.0/go.mod h1:LxGGfHIYbvsFnrJtBcazb0yG24xHdDGrT/H6RB9r3+8=
go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc=
go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -784,8 +811,9 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -865,9 +893,11 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8=
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -886,8 +916,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -942,6 +973,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -952,14 +984,16 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
@ -1103,8 +1137,9 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 h1:nquqdM9+ps0JZcIiI70+tqoaIFS5Ql4ZuK8UXnz3HfE=
google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -1124,8 +1159,10 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1154,8 +1191,10 @@ gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaD
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@ -1170,8 +1209,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg=
gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
gorm.io/driver/postgres v1.2.1 h1:JDQKnF7MC51dgL09Vbydc5kl83KkVDlcXfSPJ+xhh68=

6
helper/.goreleaser.yml

@ -12,9 +12,9 @@ builds:
dir: .
main: main.go
ldflags:
- -s -w -X main.Version={{.Version}}
- -s -w -X main.Version=v{{.Version}}
- id: dtm_arm64
env: [ CGO_ENABLED=0 ]
env: [CGO_ENABLED=0]
goos:
- darwin
goarch:
@ -22,4 +22,4 @@ builds:
dir: .
main: main.go
ldflags:
- -s -w -X main.Version={{.Version}}
- -s -w -X main.Version=v{{.Version}}

11
helper/Dockerfile-release

@ -1,5 +1,11 @@
# syntax=docker/dockerfile:1
FROM --platform=$TARGETPLATFORM golang:1.16-alpine as builder
# FROM node:14.19-alpine as builder1
# ARG RELEASE_VERSION
# WORKDIR /app/dtm
# COPY . .
# RUN cd admin && yarn && VITE_ADMIN_VERSION=$RELEASE_VERSION yarn build
FROM --platform=$TARGETPLATFORM golang:1.16-alpine as builder2
ARG TARGETARCH
ARG TARGETOS
ARG RELEASE_VERSION
@ -7,9 +13,10 @@ WORKDIR /app/dtm
# RUN go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
EXPOSE 8080
COPY . .
# COPY --from=builder1 /app/dtm/admin/dist /app/dtm/admin
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags="-s -w -X main.Version=$RELEASE_VERSION"
FROM --platform=$TARGETPLATFORM alpine
COPY --from=builder /app/dtm/dtm /app/dtm/
COPY --from=builder2 /app/dtm/dtm /app/dtm/
WORKDIR /app/dtm
ENTRYPOINT ["/app/dtm/dtm"]

73
helper/README-cn.md

@ -9,14 +9,7 @@
# 跨语言分布式事务管理器
DTM是一款变革性的分布式事务框架,提供了傻瓜式的使用方式,极大的降低了分布式事务的使用门槛,改变了“能不用分布式事务就不用”的行业现状。 dtm 的应用范围非常广,可以应用于以下常见的领域:
- [秒杀系统,保证Redis中精准的库存,和最终创建的订单完全一致,无需手动调整](https://dtm.pub/app/flash.html)
- [保证缓存与DB的一致性](https://dtm.pub/app/cache.html)
- [非单体的订单系统,大幅简化架构](https://dtm.pub/app/order.html)
- 微服务架构(已原生支持[go-zero](https://github.com/zeromicro/go-zero)等框架)中跨服务更新数据保证一致性
他优雅的解决了幂等、空补偿、悬挂等分布式事务难题,提供跨语言,跨存储引擎组合事务的强大功能:
<img src="https://pica.zhimg.com/80/v2-2f66cb3074e68d38c29694318680acac_1440w.png" height=250 />
DTM是一款变革性的分布式事务框架,提供了傻瓜式的使用方式,极大的降低了分布式事务的使用门槛,改变了“能不用分布式事务就不用”的行业现状,优雅的解决了服务间的数据一致性问题。
## 谁在使用DTM(仅列出部分)
[Tencent 腾讯](https://dtm.pub/other/using.html#tencent)
@ -25,39 +18,26 @@ DTM是一款变革性的分布式事务框架,提供了傻瓜式的使用方
[Ivydad 常青藤爸爸](https://dtm.pub/other/using.html#ivydad)
## 亮点
* 极易上手
- 零配置启动服务,提供非常简单的HTTP接口,极大降低上手分布式事务的难度,新手也能快速接入
* 跨语言
- 可适合多语言栈的公司使用。方便go、python、php、nodejs、ruby、c# 各类语言使用。
* 使用简单
- 开发者不再担心悬挂、空补偿、幂等各类问题,首创子事务屏障技术代为处理
* 易部署、易扩展
- 依赖mysql/redis,部署简单,易集群化,易水平扩展
* 多种分布式事务协议支持
- TCC、SAGA、XA、二阶段消息,一站式解决所有分布式事务问题
## 与其他框架对比
非Java语言类的,暂未看到除dtm之外的成熟框架,因此这里将DTM和Java中最成熟的Seata对比:
[更多](https://dtm.pub/other/using.html)
| 特性| DTM | SEATA |备注|
|:-----:|:----:|:----:|:----:|
|[支持语言](https://dtm.pub/other/opensource.html#lang) |<span style="color:green">Go、c#、Java、python、php...</span>|<span style="color:orange">Java</span>|dtm可轻松接入一门新语言|
|[存储引擎](https://dtm.pub/other/opensource.html#store) |<span style="color:green">支持数据库、Redis、Mongo等</span>|<span style="color:orange">数据库</span>||
|[异常处理](https://dtm.pub/other/opensource.html#exception)| <span style="color:green"> 子事务屏障自动处理 </span>|<span style="color:orange">手动处理</span> |dtm解决了幂等、悬挂、空补偿|
|[SAGA事务](https://dtm.pub/other/opensource.html#saga) |<span style="color:green">极简易用</span> |<span style="color:orange">复杂状态机</span> ||
|[二阶段消息](https://dtm.pub/other/opensource.html#msg)|<span style="color:green"></span>|<span style="color:red"></span>|最简消息最终一致性架构|
|[TCC事务](https://dtm.pub/other/opensource.html#tcc)| <span style="color:green"></span>|<span style="color:green"></span>||
|[XA事务](https://dtm.pub/other/opensource.html#xa)|<span style="color:green"></span>|<span style="color:green"></span>||
|[AT事务](https://dtm.pub/other/opensource.html#at)|<span style="color:orange">建议使用XA</span>|<span style="color:green"></span>|AT与XA类似,但有脏回滚|
|[单服务多数据源](https://dtm.pub/other/opensource.html#multidb)|<span style="color:green"></span>|<span style="color:red"></span>||
|[通信协议](https://dtm.pub/other/opensource.html#protocol)|HTTP、gRPC、go-zero|dubbo等协议|dtm对云原生更加友好|
如果贵公司也已使用 dtm,欢迎在 [登记地址](https://github.com/dtm-labs/dtm/issues/7) 登记,仅仅为了推广,不做其它用途。
从上面对比的特性来看,dtm在许多方面都具备很大的优势。如果考虑多语言支持、多存储引擎支持,那么dtm毫无疑问是您的首选
## 特性
* 支持多种语言:支持Go、Java、PHP、C#、Python、Nodejs 各种语言的SDK
* 支持多种事务模式:SAGA、TCC、XA
* 支持消息最终一致性:二阶段消息,比本地消息表更优雅的方案
* 未支持 AT 事务模式,建议使用XA,详情参见[XA vs AT](https://dtm.pub/practice/at)
* 支持多种数据库事务:Mysql、Redis、MongoDB、Postgres、TDSQL等
* 支持多种存储引擎:Mysql(常用)、Redis(高性能)、MongoDB(规划中)
* 支持多种微服务架构:[go-zero](https://github.com/zeromicro/go-zero)、go-kratos/kratos、polarismesh/polaris
* 支持高可用,易水平扩展
详细的对比可以点击特性中的链接,跳到相关文档
## 应用场景:
DTM 可以应用于大量的场景下的数据一致性问题,以下是几个常见场景
* [缓存管理](https://dtm.pub/app/cache.html):彻底保证缓存最终一致及强一致
* [秒杀扣库存](https://dtm.pub/app/flash.html):极端情况下,也能保证Redis中精准的库存,和最终创建的订单完全一致,无需手动调整
* [非单体的订单系统](https://dtm.pub/app/order.html): 大幅简化架构
* [事件发布/订阅](https://dtm.pub/practice/msg.html):更好的发件箱模式
## [性能测试报告](https://dtm.pub/other/performance.html)
@ -65,12 +45,6 @@ DTM是一款变革性的分布式事务框架,提供了傻瓜式的使用方
## [各语言客户端及示例](https://dtm.pub/ref/sdk.html#go)
## 微服务框架支持
- [go-zero](https://github.com/zeromicro/go-zero):一开源就非常火爆的微服务框架,首家接入dtm的微服务框架。感谢go-zero作者[kevwan](https://github.com/kevwan)的大力支持
- [kratos](https://github.com/go-kratos/kratos):这是bilibili开源的一个微服务框架。感谢[lei liu](https://github.com/Leizhengzi)的贡献
- [polaris](https://github.com/polarismesh/polaris): 腾讯开源的注册发现组件,以及在其上构建的微服务框架。感谢腾讯同学[ychensha](https://github.com/ychensha)的PR
- 其他:看用户需求量,择机接入,参见[微服务支持](https://dtm.pub/ref/proto.html)
## 快速开始
如果您不是Go语言,可以跳转[各语言客户端及示例](https://dtm.pub/ref/sdk.html#go),里面有相关的快速开始示例
@ -100,7 +74,7 @@ go run main.go
req := &gin.H{"amount": 30} // 微服务的载荷
// DtmServer为DTM服务的地址,是一个url
DtmServer := "http://localhost:36789/api/dtmsvr"
saga := dtmcli.NewSaga(DtmServer, dtmcli.MustGenGid(DtmServer)).
saga := dtmcli.NewSaga(DtmServer, shortuuid.New()).
// 添加一个TransOut的子事务,正向操作为url: qsBusi+"/TransOut", 补偿操作为url: qsBusi+"/TransOutCom"
Add(qsBusi+"/TransOut", qsBusi+"/TransOutCom", req).
// 添加一个TransIn的子事务,正向操作为url: qsBusi+"/TransIn", 补偿操作为url: qsBusi+"/TransInCom"
@ -137,10 +111,11 @@ go run main.go
上述示例主要演示了分布式事务的流程,更多的内容,包括如何与实际的数据库对接,如何做补偿,如何做回滚等实际的例子,请参考[dtm-labs/dtm-examples](https://github.com/dtm-labs/dtm-examples)
## 联系我们
### 公众号
dtm官方公众号:分布式事务,大量干货分享,以及dtm的最新消息
### 交流群
请加 yedf2008 好友或者扫码加好友,验证回复 dtm 按照指引进群
### 微信交流群
如果您希望更快的获得反馈,或者更多的了解其他用户在使用过程中的各种反馈,欢迎加入我们的微信交流群
请加作者的微信 yedf2008 好友或者扫码加好友,备注 `dtm` 按照指引进群
![yedf2008](http://service.ivydad.com/cover/dubbingb6b5e2c0-2d2a-cd59-f7c5-c6b90aceb6f1.jpeg)

10
helper/README-en.md

@ -17,11 +17,13 @@ DTM is a distributed transaction framework which provides cross-service eventual
## Who's using DTM (partial)
[Tencent](https://www.tencent.com/)
[Tencent](https://en.dtm.pub/other/using.html#tencent)
[Bytedance](https://www.bytedance.com/)
[Bytedance](https://en.dtm.pub/other/using.html#bytedance)
[Ivydad](https://ivydad.com)
[Ivydad](https://en.dtm.pub/other/using.html#ivydad)
[More](https://en.dtm.pub/other/using.html)
## [Cook Book](https://en.dtm.pub)
@ -54,7 +56,7 @@ go run main.go
DtmServer := "http://localhost:36789/api/dtmsvr"
req := &gin.H{"amount": 30} // micro-service payload
// DtmServer is the address of DTM micro-service
saga := dtmcli.NewSaga(DtmServer, dtmcli.MustGenGid(DtmServer)).
saga := dtmcli.NewSaga(DtmServer, shortuuid.New()).
// add a TransOut subtraction,forward operation with url: qsBusi+"/TransOut", reverse compensation operation with url: qsBusi+"/TransOutCom"
Add(qsBusi+"/TransOut", qsBusi+"/TransOutCom", req).
// add a TransIn subtraction, forward operation with url: qsBusi+"/TransIn", reverse compensation operation with url: qsBusi+"/TransInCom"

16
helper/bench/svr/http.go

@ -53,7 +53,7 @@ func reloadData() {
db := pdbGet()
tables := []string{"dtm_busi.user_account", "dtm_busi.user_account_log", "dtm.trans_global", "dtm.trans_branch_op", "dtm_barrier.barrier"}
for _, t := range tables {
_, err := dtmimp.DBExec(db, fmt.Sprintf("truncate %s", t))
_, err := dtmimp.DBExec(busi.BusiConf.Driver, db, fmt.Sprintf("truncate %s", t))
logger.FatalIfError(err)
}
s := "insert ignore into dtm_busi.user_account(user_id, balance) values "
@ -61,7 +61,7 @@ func reloadData() {
for i := 1; i <= total; i++ {
ss = append(ss, fmt.Sprintf("(%d, 1000000)", i))
}
_, err := dtmimp.DBExec(db, s+strings.Join(ss, ","))
_, err := dtmimp.DBExec(busi.BusiConf.Driver, db, s+strings.Join(ss, ","))
logger.FatalIfError(err)
logger.Debugf("%d users inserted. used: %dms", total, time.Since(began).Milliseconds())
}
@ -73,11 +73,11 @@ var sqls = 1
// PrepareBenchDB prepares db data for bench
func PrepareBenchDB() {
db := pdbGet()
_, err := dtmimp.DBExec(db, "CREATE DATABASE if not exists dtm_busi")
_, err := dtmimp.DBExec(busi.BusiConf.Driver, db, "CREATE DATABASE if not exists dtm_busi")
logger.FatalIfError(err)
_, err = dtmimp.DBExec(db, "drop table if exists dtm_busi.user_account_log")
_, err = dtmimp.DBExec(busi.BusiConf.Driver, db, "drop table if exists dtm_busi.user_account_log")
logger.FatalIfError(err)
_, err = dtmimp.DBExec(db, `create table if not exists dtm_busi.user_account_log (
_, err = dtmimp.DBExec(busi.BusiConf.Driver, db, `create table if not exists dtm_busi.user_account_log (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
user_id INT(11) NOT NULL,
delta DECIMAL(11, 2) not null,
@ -111,10 +111,10 @@ func qsAdjustBalance(uid int, amount int, c *gin.Context) error { // nolint: unp
tb := dtmimp.TransBaseFromQuery(c.Request.URL.Query())
f := func(tx *sql.Tx) error {
for i := 0; i < sqls; i++ {
_, err := dtmimp.DBExec(tx, "insert into dtm_busi.user_account_log(user_id, delta, gid, branch_id, op, reason) values(?,?,?,?,?,?)",
_, err := dtmimp.DBExec(busi.BusiConf.Driver, tx, "insert into dtm_busi.user_account_log(user_id, delta, gid, branch_id, op, reason) values(?,?,?,?,?,?)",
uid, amount, tb.Gid, c.Query("branch_id"), tb.TransType, fmt.Sprintf("inserted by dtm transaction %s %s", tb.Gid, c.Query("branch_id")))
logger.FatalIfError(err)
_, err = dtmimp.DBExec(tx, "update dtm_busi.user_account set balance = balance + ?, update_time = now() where user_id = ?", amount, uid)
_, err = dtmimp.DBExec(busi.BusiConf.Driver, tx, "update dtm_busi.user_account set balance = balance + ?, update_time = now() where user_id = ?", amount, uid)
logger.FatalIfError(err)
}
return nil
@ -122,7 +122,7 @@ func qsAdjustBalance(uid int, amount int, c *gin.Context) error { // nolint: unp
if strings.Contains(mode, "barrier") {
barrier, err := dtmcli.BarrierFromQuery(c.Request.URL.Query())
logger.FatalIfError(err)
err = barrier.Call(txGet(), f)
err = barrier.CallWithDB(pdbGet(), f)
logger.FatalIfError(err)
} else {
tx := txGet()

28
helper/compose.cloud.yml

@ -1,28 +0,0 @@
version: '3.3'
services:
api:
build: ..
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ..:/app/dtm
extra_hosts:
- 'host.docker.internal:host-gateway'
environment:
IS_DOCKER: 1
ports:
- '9080:8080'
mysql:
image: 'mysql:5.7'
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
command:
[
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_unicode_ci',
]
ports:
- '3306:3306'

16
helper/compose.mysql.yml

@ -1,16 +0,0 @@
version: '3.3'
services:
mysql:
image: 'mysql:5.7'
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
command:
[
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_unicode_ci',
]
ports:
- '3306:3306'

13
helper/compose.postgres.yml

@ -1,13 +0,0 @@
version: '3.3'
services:
postgres:
image: 'postgres:13'
command: postgres --max_prepared_transactions=1000
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
environment:
POSTGRES_PASSWORD: mysecretpassword
ports:
- '5432:5432'

1
helper/compose.store.yml

@ -22,6 +22,7 @@ services:
- /etc/timezone:/etc/timezone:ro
environment:
POSTGRES_PASSWORD: mysecretpassword
POSTGRES_DB: dtm
ports:
- '5432:5432'

2
helper/golint.sh

@ -1,4 +1,4 @@
set -x
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.41.0
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.2
$(go env GOPATH)/bin/golangci-lint run

2
helper/test-cover.sh

@ -1,6 +1,6 @@
set -x
echo "" > coverage.txt
for store in redis mysql boltdb; do
for store in redis mysql boltdb postgres; do
for d in $(go list ./... | grep -v vendor); do
TEST_STORE=$store go test -covermode count -coverprofile=profile.out -coverpkg=github.com/dtm-labs/dtm/dtmcli,github.com/dtm-labs/dtm/dtmcli/dtmimp,github.com/dtm-labs/dtm/dtmcli/logger,github.com/dtm-labs/dtm/dtmgrpc,github.com/dtm-labs/dtm/dtmgrpc/dtmgimp,github.com/dtm-labs/dtm/dtmsvr,github.com/dtm-labs/dtm/dtmsvr/config,github.com/dtm-labs/dtm/dtmsvr/storage,github.com/dtm-labs/dtm/dtmsvr/storage/boltdb,github.com/dtm-labs/dtm/dtmsvr/storage/redis,github.com/dtm-labs/dtm/dtmsvr/storage/registry,github.com/dtm-labs/dtm/dtmsvr/storage/sql,github.com/dtm-labs/dtm/dtmutil -gcflags=-l $d || exit 1
if [ -f profile.out ]; then

71
main.go

@ -7,12 +7,20 @@
package main
import (
"embed"
"fmt"
"io/fs"
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"github.com/dtm-labs/dtm/dtmcli/logger"
"github.com/dtm-labs/dtm/dtmsvr/config"
"github.com/dtm-labs/dtm/dtmsvr/entry"
_ "github.com/dtm-labs/dtm/dtmsvr/microservices"
"github.com/gin-gonic/gin"
@ -22,23 +30,61 @@ import (
var Version string
func main() {
app := entry.Main(&Version)
app, conf := entry.Main(&Version)
if app != nil {
addDashboard(app)
select {}
addAdmin(app, conf)
q := make(chan os.Signal, 1)
signal.Notify(q, syscall.SIGINT, syscall.SIGTERM)
<-q
logger.Infof("Shutdown dtm server...")
}
}
func addDashboard(app *gin.Engine) {
app.GET("/dashboard/*name", proxyDashboard)
app.GET("/@vite/*name", proxyDashboard)
app.GET("/node_modules/*name", proxyDashboard)
app.GET("/src/*name", proxyDashboard)
app.GET("/@id/*name", proxyDashboard)
//go:embed admin/dist
var admin embed.FS
var target = ""
func getSub(f1 fs.FS, sub string) fs.FS {
f2, err := fs.Sub(f1, sub)
logger.FatalIfError(err)
return f2
}
func addAdmin(app *gin.Engine, conf *config.Type) {
// for released dtm, serve admin from local files because the build output has been embed
// for testing users, proxy admin to target because the build output has not been embed
dist := getSub(admin, "admin/dist")
index, err := dist.Open("index.html")
if err == nil {
cont, err := ioutil.ReadAll(index)
logger.FatalIfError(err)
_ = index.Close()
sfile := string(cont)
renderIndex := func(c *gin.Context) {
c.Header("content-type", "text/html;charset=utf-8")
c.String(200, sfile)
}
app.StaticFS("/assets", http.FS(getSub(dist, "assets")))
app.GET("/admin/*name", renderIndex)
app.GET("/", renderIndex)
logger.Infof("admin is served from dir 'admin/dist/'")
} else {
app.GET("/", proxyAdmin)
app.GET("/assets/*name", proxyAdmin)
app.GET("/admin/*name", proxyAdmin)
lang := os.Getenv("LANG")
if strings.HasPrefix(lang, "zh_CN") {
target = "cn-admin.dtm.pub"
} else {
target = "admin.dtm.pub"
}
logger.Infof("admin is proxied to %s", target)
}
logger.Infof("admin is running at: http://localhost:%d", conf.HTTPPort)
}
func proxyDashboard(c *gin.Context) {
func proxyAdmin(c *gin.Context) {
target := "127.0.0.1:5000"
u := &url.URL{}
u.Scheme = "http"
u.Host = target
@ -49,7 +95,8 @@ func proxyDashboard(c *gin.Context) {
ret := fmt.Sprintf("http proxy error %v", err)
_, _ = rw.Write([]byte(ret))
}
logger.Debugf("proxy dashboard to %s", target)
logger.Debugf("proxy admin to %s", target)
c.Request.Host = target
proxy.ServeHTTP(c.Writer, c.Request)
}

13
sqls/dtmsvr.storage.mysql.sql

@ -19,6 +19,7 @@ CREATE TABLE if not EXISTS dtm.trans_global (
`next_cron_time` datetime default null comment 'next time to process this trans. for use of cron job',
`owner` varchar(128) not null default '' comment 'who is locking this trans',
`ext_data` TEXT comment 'extended data for this trans',
`rollback_reason` varchar(1024) DEFAULT '' COMMENT 'rollback reason for transaction',
PRIMARY KEY (`id`),
UNIQUE KEY `gid` (`gid`),
key `owner`(`owner`),
@ -41,3 +42,15 @@ CREATE TABLE IF NOT EXISTS dtm.trans_branch_op (
PRIMARY KEY (`id`),
UNIQUE KEY `gid_uniq` (`gid`, `branch_id`, `op`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
drop table IF EXISTS dtm.kv;
CREATE TABLE IF NOT EXISTS dtm.kv (
`id` bigint(22) NOT NULL AUTO_INCREMENT,
`cat` varchar(45) NOT NULL COMMENT 'the category of this data',
`k` varchar(128) NOT NULL,
`v` TEXT,
`version` bigint(22) default 1 COMMENT 'version of the value',
create_time datetime default NULL,
update_time datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE key `uniq_k`(`cat`, `k`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

30
sqls/dtmsvr.storage.postgres.sql

@ -1,11 +1,8 @@
CREATE SCHEMA if not EXISTS dtm
/* SQLINES DEMO *** RACTER SET utf8mb4 */
;
drop table IF EXISTS dtm.trans_global;
-- SQLINES LICENSE FOR EVALUATION USE ONLY
CREATE SEQUENCE if not EXISTS dtm.trans_global_seq;
CREATE TABLE if not EXISTS dtm.trans_global (
id bigint NOT NULL DEFAULT NEXTVAL ('dtm.trans_global_seq'),
drop table IF EXISTS trans_global;
CREATE SEQUENCE if not EXISTS trans_global_seq;
CREATE TABLE if not EXISTS trans_global (
id bigint NOT NULL DEFAULT NEXTVAL ('trans_global_seq'),
gid varchar(128) NOT NULL,
trans_type varchar(45) not null,
status varchar(45) NOT NULL,
@ -21,16 +18,17 @@ CREATE TABLE if not EXISTS dtm.trans_global (
next_cron_time timestamp(0) with time zone default null,
owner varchar(128) not null default '',
ext_data text,
rollback_reason varchar(1024) DEFAULT '',
PRIMARY KEY (id),
CONSTRAINT gid UNIQUE (gid)
);
create index if not EXISTS owner on dtm.trans_global(owner);
create index if not EXISTS status_next_cron_time on dtm.trans_global (status, next_cron_time);
drop table IF EXISTS dtm.trans_branch_op;
-- SQLINES LICENSE FOR EVALUATION USE ONLY
CREATE SEQUENCE if not EXISTS dtm.trans_branch_op_seq;
CREATE TABLE IF NOT EXISTS dtm.trans_branch_op (
id bigint NOT NULL DEFAULT NEXTVAL ('dtm.trans_branch_op_seq'),
create index if not EXISTS owner on trans_global(owner);
create index if not EXISTS status_next_cron_time on trans_global (status, next_cron_time);
drop table IF EXISTS trans_branch_op;
CREATE SEQUENCE if not EXISTS trans_branch_op_seq;
CREATE TABLE IF NOT EXISTS trans_branch_op (
id bigint NOT NULL DEFAULT NEXTVAL ('trans_branch_op_seq'),
gid varchar(128) NOT NULL,
url varchar(1024) NOT NULL,
data TEXT,
@ -44,4 +42,4 @@ CREATE TABLE IF NOT EXISTS dtm.trans_branch_op (
update_time timestamp(0) with time zone DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT gid_branch_uniq UNIQUE (gid, branch_id, op)
);
);

1
sqls/dtmsvr.storage.tdsql.sql

@ -19,6 +19,7 @@ CREATE TABLE if not EXISTS dtm.trans_global (
`next_cron_time` datetime default null comment 'next time to process this trans. for use of cron job',
`owner` varchar(128) not null default '' comment 'who is locking this trans',
`ext_data` TEXT comment 'extended data for this trans',
`rollback_reason` varchar(1024) DEFAULT '' COMMENT 'rollback reason for transaction',
PRIMARY KEY (`id`,`gid`),
UNIQUE KEY `id` (`id`,`gid`),
UNIQUE KEY `gid` (`gid`),

6
test/api_test.go

@ -18,6 +18,12 @@ import (
"github.com/stretchr/testify/assert"
)
func TestAPIVersion(t *testing.T) {
resp, err := dtmimp.RestyClient.R().Get(dtmutil.DefaultHTTPServer + "/version")
assert.Nil(t, err)
assert.Equal(t, 200, resp.StatusCode())
}
func TestAPIQuery(t *testing.T) {
gid := dtmimp.GetFuncName()
err := genMsg(gid).Submit()

4
test/base_test.go

@ -26,7 +26,7 @@ type BarrierModel struct {
}
// TableName gorm table name
func (BarrierModel) TableName() string { return "dtm_barrier.barrier" }
func (BarrierModel) TableName() string { return dtmimp.BarrierTableName }
func TestBaseSqlDB(t *testing.T) {
asserts := assert.New(t)
@ -38,7 +38,7 @@ func TestBaseSqlDB(t *testing.T) {
Op: dtmimp.OpAction,
BarrierID: 1,
}
db.Must().Exec("insert into dtm_barrier.barrier(trans_type, gid, branch_id, op, barrier_id, reason) values('saga', 'gid1', 'branch_id1', 'action', '01', 'saga')")
db.Must().Exec(fmt.Sprintf("insert into %s(trans_type, gid, branch_id, op, barrier_id, reason) values('saga', 'gid1', 'branch_id1', 'action', '01', 'saga')", dtmimp.BarrierTableName))
tx, err := db.ToSQLDB().Begin()
asserts.Nil(err)
err = barrier.Call(tx, func(tx *sql.Tx) error {

34
test/busi/barrier.go

@ -22,49 +22,49 @@ func init() {
setupFuncs["BarrierSetup"] = func(app *gin.Engine) {
app.POST(BusiAPI+"/SagaBTransIn", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
barrier := MustBarrierFromGin(c)
return barrier.Call(txGet(), func(tx *sql.Tx) error {
return barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransInUID, reqFrom(c).Amount, reqFrom(c).TransInResult)
})
}))
app.POST(BusiAPI+"/SagaBTransInCom", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
barrier := MustBarrierFromGin(c)
return barrier.Call(txGet(), func(tx *sql.Tx) error {
return barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransInUID, -reqFrom(c).Amount, "")
})
}))
app.POST(BusiAPI+"/SagaB2TransIn", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
barrier := MustBarrierFromGin(c)
err := barrier.Call(txGet(), func(tx *sql.Tx) error {
err := barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransInUID, reqFrom(c).Amount/2, reqFrom(c).TransInResult)
})
if err != nil {
return err
}
return barrier.Call(txGet(), func(tx *sql.Tx) error {
return barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransInUID, reqFrom(c).Amount/2, reqFrom(c).TransInResult)
})
}))
app.POST(BusiAPI+"/SagaB2TransInCom", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
barrier := MustBarrierFromGin(c)
err := barrier.Call(txGet(), func(tx *sql.Tx) error {
err := barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransInUID, -reqFrom(c).Amount/2, "")
})
if err != nil {
return err
}
return barrier.Call(txGet(), func(tx *sql.Tx) error {
return barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransInUID, -reqFrom(c).Amount, "")
})
}))
app.POST(BusiAPI+"/SagaBTransOut", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
barrier := MustBarrierFromGin(c)
return barrier.Call(txGet(), func(tx *sql.Tx) error {
return barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransOutUID, -reqFrom(c).Amount, reqFrom(c).TransOutResult)
})
}))
app.POST(BusiAPI+"/SagaBTransOutCom", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
barrier := MustBarrierFromGin(c)
return barrier.Call(txGet(), func(tx *sql.Tx) error {
return barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return SagaAdjustBalance(tx, TransOutUID, reqFrom(c).Amount, "")
})
}))
@ -82,17 +82,17 @@ func init() {
if req.TransInResult != "" {
return dtmcli.String2DtmError(req.TransInResult)
}
return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error {
return MustBarrierFromGin(c).CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return tccAdjustTrading(tx, TransInUID, req.Amount)
})
}))
app.POST(BusiAPI+"/TccBTransInConfirm", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error {
return MustBarrierFromGin(c).CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return tccAdjustBalance(tx, TransInUID, reqFrom(c).Amount)
})
}))
app.POST(BusiAPI+"/TccBTransInCancel", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error {
return MustBarrierFromGin(c).CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return tccAdjustTrading(tx, TransInUID, -reqFrom(c).Amount)
})
}))
@ -170,7 +170,7 @@ func init() {
})
}
return bb.Call(txGet(), func(tx *sql.Tx) error {
return bb.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return tccAdjustTrading(tx, TransOutUID, -req.Amount)
})
}))
@ -178,7 +178,7 @@ func init() {
if reqFrom(c).Store == Redis || reqFrom(c).Store == Mongo {
return nil
}
return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error {
return MustBarrierFromGin(c).CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return tccAdjustBalance(tx, TransOutUID, -reqFrom(c).Amount)
})
}))
@ -198,7 +198,7 @@ func TccBarrierTransOutCancel(c *gin.Context) interface{} {
return SagaMongoAdjustBalance(sc, sc.Client(), TransOutUID, reqFrom(c).Amount, "")
})
}
return bb.Call(txGet(), func(tx *sql.Tx) error {
return bb.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return tccAdjustTrading(tx, TransOutUID, reqFrom(c).Amount)
})
}
@ -212,21 +212,21 @@ func (s *busiServer) TransInBSaga(ctx context.Context, in *BusiReq) (*emptypb.Em
func (s *busiServer) TransOutBSaga(ctx context.Context, in *BusiReq) (*emptypb.Empty, error) {
barrier := MustBarrierFromGrpc(ctx)
return &emptypb.Empty{}, barrier.Call(txGet(), func(tx *sql.Tx) error {
return &emptypb.Empty{}, barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return sagaGrpcAdjustBalance(tx, TransOutUID, -in.Amount, in.TransOutResult)
})
}
func (s *busiServer) TransInRevertBSaga(ctx context.Context, in *BusiReq) (*emptypb.Empty, error) {
barrier := MustBarrierFromGrpc(ctx)
return &emptypb.Empty{}, barrier.Call(txGet(), func(tx *sql.Tx) error {
return &emptypb.Empty{}, barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return sagaGrpcAdjustBalance(tx, TransInUID, -in.Amount, "")
})
}
func (s *busiServer) TransOutRevertBSaga(ctx context.Context, in *BusiReq) (*emptypb.Empty, error) {
barrier := MustBarrierFromGrpc(ctx)
return &emptypb.Empty{}, barrier.Call(txGet(), func(tx *sql.Tx) error {
return &emptypb.Empty{}, barrier.CallWithDB(pdbGet(), func(tx *sql.Tx) error {
return sagaGrpcAdjustBalance(tx, TransOutUID, in.Amount, "")
})
}

5
test/busi/base_http.go

@ -69,7 +69,8 @@ func BaseAppStartup() *gin.Engine {
}
logger.Debugf("Starting busi at: %d", BusiPort)
go func() {
_ = app.Run(fmt.Sprintf(":%d", BusiPort))
err := app.Run(fmt.Sprintf(":%d", BusiPort))
dtmimp.FatalIfError(err)
}()
return app
}
@ -140,7 +141,7 @@ func BaseAddRoute(app *gin.Engine) {
}))
app.POST(BusiAPI+"/TransOutXa", dtmutil.WrapHandler(func(c *gin.Context) interface{} {
return dtmcli.XaLocalTransaction(c.Request.URL.Query(), BusiConf, func(db *sql.DB, xa *dtmcli.Xa) error {
return SagaAdjustBalance(db, TransOutUID, reqFrom(c).Amount, reqFrom(c).TransOutResult)
return SagaAdjustBalance(db, TransOutUID, -reqFrom(c).Amount, reqFrom(c).TransOutResult)
})
}))
app.POST(BusiAPI+"/TransOutTimeout", dtmutil.WrapHandler(func(c *gin.Context) interface{} {

1
test/busi/base_types.go

@ -142,6 +142,7 @@ type mainSwitchType struct {
QueryPreparedResult AutoEmptyString
NextResult AutoEmptyString
JrpcResult AutoEmptyString
FailureReason AutoEmptyString
}
// MainSwitch controls busi success or fail

20
test/busi/busi.go

@ -34,7 +34,7 @@ func handleGrpcBusiness(in *BusiReq, result1 string, result2 string, busi string
if res == dtmcli.ResultSuccess {
return nil
} else if res == dtmcli.ResultFailure {
return status.New(codes.Aborted, dtmcli.ResultFailure).Err()
return status.New(codes.Aborted, fmt.Sprintf("reason:%s", MainSwitch.FailureReason.Fetch())).Err()
} else if res == dtmcli.ResultOngoing {
return status.New(codes.FailedPrecondition, dtmcli.ResultOngoing).Err()
}
@ -48,6 +48,9 @@ func handleGeneralBusiness(c *gin.Context, result1 string, result2 string, busi
if res == "ERROR" {
return errors.New("ERROR from user")
}
if res == dtmimp.ResultFailure {
return fmt.Errorf("reason:%s. %w", MainSwitch.FailureReason.Fetch(), dtmimp.ErrFailure)
}
return dtmcli.String2DtmError(res)
}
@ -66,7 +69,7 @@ func sagaGrpcAdjustBalance(db dtmcli.DB, uid int, amount int64, result string) e
if result == dtmcli.ResultFailure {
return status.New(codes.Aborted, dtmcli.ResultFailure).Err()
}
_, err := dtmimp.DBExec(db, "update dtm_busi.user_account set balance = balance + ? where user_id = ?", amount, uid)
_, err := dtmimp.DBExec(BusiConf.Driver, db, "update dtm_busi.user_account set balance = balance + ? where user_id = ?", amount, uid)
return err
}
@ -75,7 +78,7 @@ func SagaAdjustBalance(db dtmcli.DB, uid int, amount int, result string) error {
if strings.Contains(result, dtmcli.ResultFailure) {
return dtmcli.ErrFailure
}
_, err := dtmimp.DBExec(db, "update dtm_busi.user_account set balance = balance + ? where user_id = ?", amount, uid)
_, err := dtmimp.DBExec(BusiConf.Driver, db, "update dtm_busi.user_account set balance = balance + ? where user_id = ?", amount, uid)
return err
}
@ -102,12 +105,12 @@ func SagaMongoAdjustBalance(ctx context.Context, mc *mongo.Client, uid int, amou
return fmt.Errorf("balance not enough %w", dtmcli.ErrFailure)
}
return nil
}
func tccAdjustTrading(db dtmcli.DB, uid int, amount int) error {
affected, err := dtmimp.DBExec(db, `update dtm_busi.user_account set trading_balance=trading_balance+?
where user_id=? and trading_balance + ? + balance >= 0`, amount, uid, amount)
affected, err := dtmimp.DBExec(BusiConf.Driver, db, `update dtm_busi.user_account
set trading_balance=trading_balance+?
where user_id=? and trading_balance + ? + balance >= 0`, amount, uid, amount)
if err == nil && affected == 0 {
return fmt.Errorf("update error, maybe balance not enough")
}
@ -115,8 +118,9 @@ func tccAdjustTrading(db dtmcli.DB, uid int, amount int) error {
}
func tccAdjustBalance(db dtmcli.DB, uid int, amount int) error {
affected, err := dtmimp.DBExec(db, `update dtm_busi.user_account set trading_balance=trading_balance-?,
balance=balance+? where user_id=?`, amount, amount, uid)
affected, err := dtmimp.DBExec(BusiConf.Driver, db, `update dtm_busi.user_account
set trading_balance=trading_balance-?,
balance=balance+? where user_id=?`, amount, amount, uid)
if err == nil && affected == 0 {
return fmt.Errorf("update user_account 0 rows")
}

3
test/busi/quick_start.go

@ -7,6 +7,7 @@ import (
"github.com/dtm-labs/dtm/dtmcli"
"github.com/gin-gonic/gin"
"github.com/lithammer/shortuuid/v3"
)
// busi address
@ -59,7 +60,7 @@ const dtmServer = "http://localhost:36789/api/dtmsvr"
func QsFireRequest() string {
req := &gin.H{"amount": 30} // load of micro-service
// DtmServer is the url of dtm
saga := dtmcli.NewSaga(dtmServer, dtmcli.MustGenGid(dtmServer)).
saga := dtmcli.NewSaga(dtmServer, shortuuid.New()).
// add a TransOut subtraction,forward operation with url: qsBusi+"/TransOut", reverse compensation operation with url: qsBusi+"/TransOutCompensate"
Add(qsBusi+"/TransOut", qsBusi+"/TransOutCompensate", req).
// add a TransIn subtraction, forward operation with url: qsBusi+"/TransIn", reverse compensation operation with url: qsBusi+"/TransInCompensate"

6
test/common_test.go

@ -33,12 +33,12 @@ func testSql(t *testing.T) {
func testDbAlone(t *testing.T) {
db, err := dtmimp.StandaloneDB(conf.Store.GetDBConf())
assert.Nil(t, err)
_, err = dtmimp.DBExec(db, "select 1")
_, err = dtmimp.DBExec(conf.Store.Driver, db, "select 1")
assert.Equal(t, nil, err)
_, err = dtmimp.DBExec(db, "")
_, err = dtmimp.DBExec(conf.Store.Driver, db, "")
assert.Equal(t, nil, err)
db.Close()
_, err = dtmimp.DBExec(db, "select 1")
_, err = dtmimp.DBExec(conf.Store.Driver, db, "select 1")
assert.NotEqual(t, nil, err)
}

4
test/dtmsvr_test.go

@ -26,6 +26,10 @@ func getTransStatus(gid string) string {
return dtmsvr.GetTransGlobal(gid).Status
}
func getTrans(gid string) *dtmsvr.TransGlobal {
return dtmsvr.GetTransGlobal(gid)
}
func getBranchesStatus(gid string) []string {
branches := dtmsvr.GetStore().FindBranches(gid)
status := []string{}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save