Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
cache
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Odysseus
cache
Commits
e6a55d33
Commit
e6a55d33
authored
Jan 19, 2024
by
luxq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement payment
parent
fc2e062f
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
312 additions
and
0 deletions
+312
-0
go.mod
go.mod
+18
-0
go.sum
go.sum
+23
-0
payment.go
payment.go
+139
-0
payment_test.go
payment_test.go
+65
-0
redislock.go
redislock.go
+67
-0
No files found.
go.mod
0 → 100644
View file @
e6a55d33
module payment
go 1.18
require (
github.com/gomodule/redigo v1.8.9
github.com/google/uuid v1.5.0
github.com/redis/go-redis/v9 v9.4.0
github.com/stretchr/testify v1.7.0
)
require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
go.sum
0 → 100644
View file @
e6a55d33
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
payment.go
0 → 100644
View file @
e6a55d33
package
payment
import
(
"context"
"github.com/redis/go-redis/v9"
"time"
)
type
Payment
struct
{
rdb
*
redis
.
Client
}
func
NewPayment
(
redisConfig
RedisConnParam
)
*
Payment
{
return
&
Payment
{
rdb
:
redis
.
NewClient
(
&
redis
.
Options
{
Addr
:
redisConfig
.
Addr
,
Password
:
redisConfig
.
Password
,
DB
:
redisConfig
.
DbIndex
,
}),
}
}
func
(
p
*
Payment
)
IncrBalance
(
ctx
context
.
Context
,
uid
string
,
bal
int64
)
(
int64
,
error
)
{
key
:=
"balance:"
+
uid
for
{
locked
,
release
,
err
:=
tryAcquire
(
ctx
,
p
.
rdb
,
key
,
5
*
time
.
Second
)
if
err
!=
nil
{
return
0
,
err
}
if
!
locked
{
continue
}
defer
release
()
return
p
.
rdb
.
IncrBy
(
ctx
,
key
,
bal
)
.
Result
()
}
}
func
(
p
*
Payment
)
GetBalance
(
ctx
context
.
Context
,
uid
string
)
(
int64
,
error
)
{
return
p
.
rdb
.
Get
(
ctx
,
"balance:"
+
uid
)
.
Int64
()
}
func
(
p
*
Payment
)
DecrBalance
(
ctx
context
.
Context
,
uid
string
,
bal
int64
)
(
int64
,
error
)
{
key
:=
"balance:"
+
uid
for
{
locked
,
release
,
err
:=
tryAcquire
(
ctx
,
p
.
rdb
,
key
,
5
*
time
.
Second
)
if
err
!=
nil
{
return
0
,
err
}
if
!
locked
{
continue
}
defer
release
()
return
p
.
rdb
.
DecrBy
(
ctx
,
key
,
bal
)
.
Result
()
}
}
// also implement three method with key charge
func
(
p
*
Payment
)
IncrCharge
(
ctx
context
.
Context
,
uid
string
,
bal
int64
)
(
int64
,
error
)
{
key
:=
"charge:"
+
uid
for
{
locked
,
release
,
err
:=
tryAcquire
(
ctx
,
p
.
rdb
,
key
,
5
*
time
.
Second
)
if
err
!=
nil
{
return
0
,
err
}
if
!
locked
{
continue
}
defer
release
()
return
p
.
rdb
.
IncrBy
(
ctx
,
key
,
bal
)
.
Result
()
}
}
// implement GetCharge
func
(
p
*
Payment
)
GetCharge
(
ctx
context
.
Context
,
uid
string
)
(
int64
,
error
)
{
return
p
.
rdb
.
Get
(
ctx
,
"charge:"
+
uid
)
.
Int64
()
}
// implement DecrCharge
func
(
p
*
Payment
)
DecrCharge
(
ctx
context
.
Context
,
uid
string
,
bal
int64
)
(
int64
,
error
)
{
key
:=
"charge:"
+
uid
for
{
locked
,
release
,
err
:=
tryAcquire
(
ctx
,
p
.
rdb
,
key
,
5
*
time
.
Second
)
if
err
!=
nil
{
return
0
,
err
}
if
!
locked
{
continue
}
defer
release
()
return
p
.
rdb
.
DecrBy
(
ctx
,
key
,
bal
)
.
Result
()
}
}
// implement IncrCredits
func
(
p
*
Payment
)
IncrCredits
(
ctx
context
.
Context
,
uid
string
,
bal
int64
)
(
int64
,
error
)
{
key
:=
"credits:"
+
uid
for
{
locked
,
release
,
err
:=
tryAcquire
(
ctx
,
p
.
rdb
,
key
,
5
*
time
.
Second
)
if
err
!=
nil
{
return
0
,
err
}
if
!
locked
{
continue
}
defer
release
()
return
p
.
rdb
.
IncrBy
(
ctx
,
key
,
bal
)
.
Result
()
}
}
// implement GetCredits
func
(
p
*
Payment
)
GetCredits
(
ctx
context
.
Context
,
uid
string
)
(
int64
,
error
)
{
return
p
.
rdb
.
Get
(
ctx
,
"credits:"
+
uid
)
.
Int64
()
}
// implement DecrCredits
func
(
p
*
Payment
)
DecrCredits
(
ctx
context
.
Context
,
uid
string
,
bal
int64
)
(
int64
,
error
)
{
key
:=
"credits:"
+
uid
for
{
locked
,
release
,
err
:=
tryAcquire
(
ctx
,
p
.
rdb
,
key
,
5
*
time
.
Second
)
if
err
!=
nil
{
return
0
,
err
}
if
!
locked
{
continue
}
defer
release
()
return
p
.
rdb
.
DecrBy
(
ctx
,
key
,
bal
)
.
Result
()
}
}
func
(
p
*
Payment
)
Close
()
error
{
return
p
.
rdb
.
Close
()
}
payment_test.go
0 → 100644
View file @
e6a55d33
package
payment
import
(
"context"
"github.com/stretchr/testify/assert"
"testing"
)
func
TestPayment
(
t
*
testing
.
T
)
{
// Initialize a new Payment instance
p
:=
NewPayment
(
RedisConnParam
{
Addr
:
"127.0.0.1:6379"
,
Password
:
"123456"
,
DbIndex
:
0
,
})
t
.
Run
(
"Test Balance"
,
func
(
t
*
testing
.
T
)
{
// Test IncrBalance
_
,
err
:=
p
.
IncrBalance
(
context
.
Background
(),
"testuser"
,
100
)
assert
.
NoError
(
t
,
err
)
// Test GetBalance
balance
,
err
:=
p
.
GetBalance
(
context
.
Background
(),
"testuser"
)
assert
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
int64
(
100
),
balance
)
// Test DecrBalance
_
,
err
=
p
.
DecrBalance
(
context
.
Background
(),
"testuser"
,
50
)
assert
.
NoError
(
t
,
err
)
})
t
.
Run
(
"Test Charge"
,
func
(
t
*
testing
.
T
)
{
// Test IncrCharge
_
,
err
:=
p
.
IncrCharge
(
context
.
Background
(),
"testuser"
,
200
)
assert
.
NoError
(
t
,
err
)
// Test GetCharge
charge
,
err
:=
p
.
GetCharge
(
context
.
Background
(),
"testuser"
)
assert
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
int64
(
200
),
charge
)
// Test DecrCharge
_
,
err
=
p
.
DecrCharge
(
context
.
Background
(),
"testuser"
,
100
)
assert
.
NoError
(
t
,
err
)
})
t
.
Run
(
"Test Credits"
,
func
(
t
*
testing
.
T
)
{
// Test IncrCredits
_
,
err
:=
p
.
IncrCredits
(
context
.
Background
(),
"testuser"
,
300
)
assert
.
NoError
(
t
,
err
)
// Test GetCredits
credits
,
err
:=
p
.
GetCredits
(
context
.
Background
(),
"testuser"
)
assert
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
int64
(
300
),
credits
)
// Test DecrCredits
_
,
err
=
p
.
DecrCredits
(
context
.
Background
(),
"testuser"
,
150
)
assert
.
NoError
(
t
,
err
)
})
// Close the Payment instance
err
:=
p
.
Close
()
assert
.
NoError
(
t
,
err
)
}
redislock.go
0 → 100644
View file @
e6a55d33
package
payment
import
(
"context"
"fmt"
"github.com/gomodule/redigo/redis"
"github.com/google/uuid"
goredislib
"github.com/redis/go-redis/v9"
"strconv"
"strings"
"time"
)
type
RedisConnParam
struct
{
Addr
string
Password
string
DbIndex
int
}
func
tryAcquire
(
ctx
context
.
Context
,
rs
*
goredislib
.
Client
,
lockKey
string
,
lockTimeout
time
.
Duration
)
(
acquired
bool
,
release
func
(),
_
error
)
{
timeout
:=
time
.
Now
()
.
Add
(
lockTimeout
)
.
UnixNano
()
lockToken
:=
fmt
.
Sprintf
(
"%d,%s"
,
timeout
,
uuid
.
New
()
.
String
())
release
=
func
()
{
// Best effort to check we're releasing the lock we think we have. Note that it
// is still technically possible the lock token has changed between the GET and
// DEL since these are two separate operations, i.e. when the current lock happen
// to be expired at this very moment.
get
,
_
:=
rs
.
Get
(
ctx
,
lockKey
)
.
Result
()
if
get
==
lockToken
{
_
=
rs
.
Del
(
ctx
,
lockKey
)
}
}
set
,
err
:=
rs
.
SetNX
(
ctx
,
lockKey
,
lockToken
,
lockTimeout
)
.
Result
()
if
err
!=
nil
{
return
false
,
nil
,
err
}
else
if
set
{
return
true
,
release
,
nil
}
// We didn't get the lock, but we can check if the lock is expired.
currentLockToken
,
err
:=
rs
.
Get
(
ctx
,
lockKey
)
.
Result
()
if
err
==
redis
.
ErrNil
{
// Someone else got the lock and released it already.
return
false
,
nil
,
nil
}
else
if
err
!=
nil
{
return
false
,
nil
,
err
}
currentTimeout
,
_
:=
strconv
.
ParseInt
(
strings
.
SplitN
(
currentLockToken
,
","
,
2
)[
0
],
10
,
64
)
if
currentTimeout
>
time
.
Now
()
.
UnixNano
()
{
// The lock is still valid.
return
false
,
nil
,
nil
}
// The lock has expired, try to acquire it.
get
,
err
:=
rs
.
GetSet
(
ctx
,
lockKey
,
lockToken
)
.
Result
()
if
err
!=
nil
{
return
false
,
nil
,
err
}
else
if
get
!=
currentLockToken
{
// Someone else got the lock
return
false
,
nil
,
nil
}
// We got the lock.
return
true
,
release
,
nil
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment