init mongo

This commit is contained in:
daniel.w 2024-10-26 00:42:58 +08:00
parent 69746285ae
commit 9e667c83f3
22 changed files with 666 additions and 11 deletions

148
.golangci.yaml Normal file
View File

@ -0,0 +1,148 @@
run:
timeout: 3m
# Exit code when at least one issue was found.
# Default: 1
issues-exit-code: 2
# Include test files or not.
# Default: true
tests: false
# Reference URL: https://golangci-lint.run/usage/linters/
linters:
# Disable everything by default so upgrades to not include new - default
# enabled- linters.
disable-all: true
# Specifically enable linters we want to use.
enable:
# - depguard
- errcheck
# - godot
- gofmt
- goimports
- gosimple
- govet
- ineffassign
- misspell
- revive
# - staticcheck
- typecheck
- unused
# - wsl
- asasalint
- asciicheck
- bidichk
- bodyclose
# - containedctx
- contextcheck
# - cyclop
# - varnamelen
# - gci
- wastedassign
- whitespace
# - wrapcheck
- thelper
- tparallel
- unconvert
- unparam
- usestdlibvars
- tenv
- testableexamples
- stylecheck
- sqlclosecheck
- nosprintfhostport
- paralleltest
- prealloc
- predeclared
- promlinter
- reassign
- rowserrcheck
- nakedret
- nestif
- nilerr
- nilnil
- nlreturn
- noctx
- nolintlint
- nonamedreturns
- decorder
- dogsled
# - dupl
- dupword
- durationcheck
- errchkjson
- errname
- errorlint
# - execinquery
- exhaustive
- exportloopref
- forbidigo
- forcetypeassert
# - gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
# - godox
# - goerr113
# - gofumpt
- goheader
- gomoddirectives
# - gomodguard always failed
- goprintffuncname
- gosec
- grouper
- importas
- interfacebloat
# - ireturn
- lll
- loggercheck
- maintidx
- makezero
issues:
exclude-rules:
- path: _test\.go
linters:
- funlen
- goconst
- interfacer
- dupl
- lll
- goerr113
- errcheck
- gocritic
- cyclop
- wrapcheck
- gocognit
- contextcheck
exclude-dirs:
- internal/model
exclude-files:
- .*_test.go
linters-settings:
gci:
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
gocognit:
# Minimal code complexity to report.
# Default: 30 (but we recommend 10-20)
min-complexity: 40
nestif:
# Minimal complexity of if statements to report.
# Default: 5
min-complexity: 10
lll:
# Max line length, lines longer will be reported.
# '\t' is counted as 1 character by default, and can be changed with the tab-width option.
# Default: 120.
line-length: 200
# Tab width in spaces.
# Default: 1
tab-width: 1

View File

@ -4,3 +4,26 @@ Etcd:
Hosts:
- 127.0.0.1:2379
Key: trade.rpc
mongodb:
hosts: [ 127.0.0.1:27017 ]
username:
password:
databaseName: "cnx_commission"
minPoolSize: 10
maxPoolSize: 30
maxConnIdleTime: 30m
replicaName: ""
Mongo:
Schema: mongodb
Host: 127.0.0.1
User: ""
Password: ""
Port: "27017"
Database: digimon_order
ReplicaName: "order"
MaxStaleness: 30m
MaxPoolSize: 30
MinPoolSize: 10
MaxConnIdleTime: 30m

17
go.mod
View File

@ -3,7 +3,12 @@ module app-cloudep-trade-service
go 1.22.3
require (
code.30cm.net/digimon/library-go/errs v1.2.5
code.30cm.net/digimon/library-go/validator v1.0.0
github.com/go-playground/validator/v10 v10.22.0
github.com/shopspring/decimal v1.4.0
github.com/zeromicro/go-zero v1.7.3
go.mongodb.org/mongo-driver v1.17.1
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1
)
@ -18,14 +23,18 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
@ -34,11 +43,13 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
@ -48,6 +59,10 @@ require (
github.com/prometheus/procfs v0.15.1 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
@ -66,8 +81,10 @@ require (
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect

View File

@ -1,7 +1,27 @@
package config
import "github.com/zeromicro/go-zero/zrpc"
import (
"time"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
zrpc.RpcServerConf
Mongo struct {
Schema string
User string
Password string
Host string
Port string
Database string
ReplicaName string
MaxStaleness time.Duration
MaxPoolSize uint64
MinPoolSize uint64
MaxConnIdleTime time.Duration
// Compressors []string
// EnableStandardReadWriteSplitMode bool
}
}

View File

@ -0,0 +1,52 @@
package mongo
import (
"fmt"
"reflect"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type MgoDecimal struct{}
var (
_ bsoncodec.ValueEncoder = &MgoDecimal{}
_ bsoncodec.ValueDecoder = &MgoDecimal{}
)
func (dc *MgoDecimal) EncodeValue(_ bsoncodec.EncodeContext, w bsonrw.ValueWriter, value reflect.Value) error {
// TODO 待確認是否有非decimal.Decimal type而導致error的場景
dec, ok := value.Interface().(decimal.Decimal)
if !ok {
return fmt.Errorf("value %v to encode is not of type decimal.Decimal", value)
}
// Convert decimal.Decimal to primitive.Decimal128.
primDec, err := primitive.ParseDecimal128(dec.String())
if err != nil {
return fmt.Errorf("converting decimal.Decimal %v to primitive.Decimal128 error: %w", dec, err)
}
return w.WriteDecimal128(primDec)
}
func (dc *MgoDecimal) DecodeValue(_ bsoncodec.DecodeContext, r bsonrw.ValueReader, value reflect.Value) error {
primDec, err := r.ReadDecimal128()
if err != nil {
return fmt.Errorf("reading primitive.Decimal128 from ValueReader error: %w", err)
}
// Convert primitive.Decimal128 to decimal.Decimal.
dec, err := decimal.NewFromString(primDec.String())
if err != nil {
return fmt.Errorf("converting primitive.Decimal128 %v to decimal.Decimal error: %w", primDec, err)
}
// set as decimal.Decimal type
value.Set(reflect.ValueOf(dec))
return nil
}

View File

@ -0,0 +1,14 @@
package mongo
import (
"app-cloudep-trade-service/internal/config"
"fmt"
)
func MustMongoConnectURL(c config.Config) string {
return fmt.Sprintf("%s://%s:%s",
c.Mongo.Schema,
c.Mongo.Host,
c.Mongo.Port,
)
}

View File

@ -0,0 +1,51 @@
package mongo
import (
"app-cloudep-trade-service/internal/config"
"reflect"
"github.com/shopspring/decimal"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo/options"
)
type TypeCodec struct {
ValueType reflect.Type
Encoder bsoncodec.ValueEncoder
Decoder bsoncodec.ValueDecoder
}
// WithTypeCodec registers TypeCodecs to convert custom types.
func WithTypeCodec(typeCodecs ...TypeCodec) mon.Option {
return func(c *options.ClientOptions) {
registry := bson.NewRegistry()
for _, v := range typeCodecs {
registry.RegisterTypeEncoder(v.ValueType, v.Encoder)
registry.RegisterTypeDecoder(v.ValueType, v.Decoder)
}
c.SetRegistry(registry)
}
}
// SetCustomDecimalType force convert primitive.Decimal128 to decimal.Decimal.
func SetCustomDecimalType() mon.Option {
return WithTypeCodec(TypeCodec{
ValueType: reflect.TypeOf(decimal.Decimal{}),
Encoder: &MgoDecimal{},
Decoder: &MgoDecimal{},
})
}
func InitMongoOptions(cfg config.Config) mon.Option {
return func(opts *options.ClientOptions) {
opts.SetMaxPoolSize(cfg.Mongo.MaxPoolSize)
opts.SetMinPoolSize(cfg.Mongo.MinPoolSize)
opts.SetMaxConnIdleTime(cfg.Mongo.MaxConnIdleTime)
opts.SetCompressors([]string{"snappy"})
// opts.SetReplicaSet(cfg.Mongo.ReplicaName)
// opts.SetWriteConcern(writeconcern.W1())
// opts.SetReadPreference(readpref.Primary())
}
}

View File

@ -0,0 +1,12 @@
package model
import (
"errors"
"github.com/zeromicro/go-zero/core/stores/mon"
)
var (
ErrNotFound = mon.ErrNotFound
ErrInvalidObjectId = errors.New("invalid objectId")
)

View File

@ -0,0 +1,27 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/mon"
)
var _ OrderModel = (*customOrderModel)(nil)
type (
// OrderModel is an interface to be customized, add more methods here,
// and implement the added methods in customOrderModel.
OrderModel interface {
orderModel
}
customOrderModel struct {
*defaultOrderModel
}
)
// NewOrderModel returns a model for the mongo.
func NewOrderModel(url, db, collection string, opts ...mon.Option) OrderModel {
conn := mon.MustNewModel(url, db, collection, opts...)
return &customOrderModel{
defaultOrderModel: newDefaultOrderModel(conn),
}
}

View File

@ -0,0 +1,74 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"time"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
type orderModel interface {
Insert(ctx context.Context, data *Order) error
FindOne(ctx context.Context, id string) (*Order, error)
Update(ctx context.Context, data *Order) (*mongo.UpdateResult, error)
Delete(ctx context.Context, id string) (int64, error)
}
type defaultOrderModel struct {
conn *mon.Model
}
func newDefaultOrderModel(conn *mon.Model) *defaultOrderModel {
return &defaultOrderModel{conn: conn}
}
func (m *defaultOrderModel) Insert(ctx context.Context, data *Order) error {
if data.ID.IsZero() {
data.ID = primitive.NewObjectID()
data.CreateAt = time.Now()
data.UpdateAt = time.Now()
}
_, err := m.conn.InsertOne(ctx, data)
return err
}
func (m *defaultOrderModel) FindOne(ctx context.Context, id string) (*Order, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, ErrInvalidObjectId
}
var data Order
err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
switch err {
case nil:
return &data, nil
case mon.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultOrderModel) Update(ctx context.Context, data *Order) (*mongo.UpdateResult, error) {
data.UpdateAt = time.Now()
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
return res, err
}
func (m *defaultOrderModel) Delete(ctx context.Context, id string) (int64, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return 0, ErrInvalidObjectId
}
res, err := m.conn.DeleteOne(ctx, bson.M{"_id": oid})
return res, err
}

View File

@ -0,0 +1,18 @@
package model
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Order struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
// TODO: Fill your own fields
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"`
}
func (p *Order) CollectionName() string {
return "order"
}

View File

@ -7,7 +7,7 @@ import (
"context"
"app-cloudep-trade-service/gen_result/pb/trade"
"app-cloudep-trade-service/internal/logic/couponservice"
couponservicelogic "app-cloudep-trade-service/internal/logic/couponservice"
"app-cloudep-trade-service/internal/svc"
)

View File

@ -7,7 +7,7 @@ import (
"context"
"app-cloudep-trade-service/gen_result/pb/trade"
"app-cloudep-trade-service/internal/logic/inventoryservice"
inventoryservicelogic "app-cloudep-trade-service/internal/logic/inventoryservice"
"app-cloudep-trade-service/internal/svc"
)

View File

@ -7,7 +7,7 @@ import (
"context"
"app-cloudep-trade-service/gen_result/pb/trade"
"app-cloudep-trade-service/internal/logic/orderservice"
orderservicelogic "app-cloudep-trade-service/internal/logic/orderservice"
"app-cloudep-trade-service/internal/svc"
)

View File

@ -7,7 +7,7 @@ import (
"context"
"app-cloudep-trade-service/gen_result/pb/trade"
"app-cloudep-trade-service/internal/logic/productservice"
productservicelogic "app-cloudep-trade-service/internal/logic/productservice"
"app-cloudep-trade-service/internal/svc"
)

View File

@ -7,7 +7,7 @@ import (
"context"
"app-cloudep-trade-service/gen_result/pb/trade"
"app-cloudep-trade-service/internal/logic/subscriptionservice"
subscriptionservicelogic "app-cloudep-trade-service/internal/logic/subscriptionservice"
"app-cloudep-trade-service/internal/svc"
)

View File

@ -7,7 +7,7 @@ import (
"context"
"app-cloudep-trade-service/gen_result/pb/trade"
"app-cloudep-trade-service/internal/logic/walletservice"
walletservicelogic "app-cloudep-trade-service/internal/logic/walletservice"
"app-cloudep-trade-service/internal/svc"
)

View File

@ -0,0 +1,20 @@
package svc
import (
"app-cloudep-trade-service/internal/config"
mgo "app-cloudep-trade-service/internal/lib/mongo"
model "app-cloudep-trade-service/internal/model/mongo"
)
// MustOrderModel 連線 order mongo 時
func MustOrderModel(c config.Config) model.OrderModel {
orderCollection := model.Order{}
return model.NewOrderModel(
mgo.MustMongoConnectURL(c),
c.Mongo.Database,
orderCollection.CollectionName(),
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(c),
)
}

View File

@ -1,13 +1,30 @@
package svc
import "app-cloudep-trade-service/internal/config"
import (
"app-cloudep-trade-service/internal/config"
model "app-cloudep-trade-service/internal/model/mongo"
ers "code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/library-go/errs/code"
vi "code.30cm.net/digimon/library-go/validator"
)
type ServiceContext struct {
Config config.Config
Config config.Config
Validate vi.Validate
OrderModel model.OrderModel
}
func NewServiceContext(c config.Config) *ServiceContext {
// TODO 改成 Trade
ers.Scope = code.CloudEPOrder
return &ServiceContext{
Config: c,
Validate: vi.MustValidator(
WithDecimalGt(),
WithDecimalGte(),
),
OrderModel: MustOrderModel(c),
}
}

55
internal/svc/validator.go Normal file
View File

@ -0,0 +1,55 @@
package svc
import (
vi "code.30cm.net/digimon/library-go/validator"
"github.com/go-playground/validator/v10"
"github.com/shopspring/decimal"
)
// WithDecimalGt 是否大於等於
func WithDecimalGt() vi.Option {
return vi.Option{
ValidatorName: "decimalGt",
ValidatorFunc: func(fl validator.FieldLevel) bool {
if val, ok := fl.Field().Interface().(string); ok {
value, err := decimal.NewFromString(val)
if err != nil {
return false
}
conditionValue, err := decimal.NewFromString(fl.Param())
if err != nil {
return false
}
return value.GreaterThan(conditionValue)
}
return true
},
}
}
// WithDecimalGte 是否大於等於
func WithDecimalGte() vi.Option {
return vi.Option{
ValidatorName: "decimalGte",
ValidatorFunc: func(fl validator.FieldLevel) bool {
if val, ok := fl.Field().Interface().(string); ok {
value, err := decimal.NewFromString(val)
if err != nil {
return false
}
conditionValue, err := decimal.NewFromString(fl.Param())
if err != nil {
return false
}
return value.GreaterThanOrEqual(conditionValue)
}
return true
},
}
}

View File

@ -0,0 +1,106 @@
package svc
import (
"testing"
vi "code.30cm.net/digimon/library-go/validator"
)
func TestWithDecimalGte(t *testing.T) {
validate := vi.MustValidator(WithDecimalGt(), WithDecimalGte())
type TestStruct struct {
Value string `validate:"decimalGte=10.50"`
}
tests := []struct {
name string
input TestStruct
wantErr bool
}{
{
name: "valid - equal",
input: TestStruct{Value: "10.50"},
wantErr: false,
},
{
name: "valid - greater",
input: TestStruct{Value: "15.00"},
wantErr: false,
},
{
name: "invalid - less",
input: TestStruct{Value: "9.99"},
wantErr: true,
},
{
name: "invalid - not a decimal",
input: TestStruct{Value: "abc"},
wantErr: true,
},
{
name: "valid - empty string",
input: TestStruct{Value: ""},
wantErr: true, // Assuming empty string is valid
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate.ValidateAll(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("TestWithDecimalGte() %s = error %v, wantErr %v", tt.name, err, tt.wantErr)
}
})
}
}
func TestWithDecimalGt(t *testing.T) {
validate := vi.MustValidator(WithDecimalGt(), WithDecimalGt())
type TestStruct struct {
Value string `validate:"decimalGt=10.50"`
}
tests := []struct {
name string
input TestStruct
wantErr bool
}{
{
name: "valid - greater",
input: TestStruct{Value: "10.51"},
wantErr: false,
},
{
name: "invalid - equal",
input: TestStruct{Value: "10.50"},
wantErr: true, // should fail because the value is equal to the condition
},
{
name: "invalid - less",
input: TestStruct{Value: "9.99"},
wantErr: true,
},
{
name: "invalid - not a decimal",
input: TestStruct{Value: "abc"},
wantErr: true,
},
{
name: "valid - empty string",
input: TestStruct{Value: ""},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate.ValidateAll(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("TestWithDecimalGt() %s = error %v, wantErr %v", tt.name, err, tt.wantErr)
}
})
}
}

View File

@ -2,7 +2,8 @@ package main
import (
"flag"
"fmt"
"github.com/zeromicro/go-zero/core/logx"
"app-cloudep-trade-service/gen_result/pb/trade"
"app-cloudep-trade-service/internal/config"
@ -44,6 +45,6 @@ func main() {
})
defer s.Stop()
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
logx.Infof("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}