feat:init project

This commit is contained in:
daniel.w 2024-07-21 23:57:56 +08:00
commit 0bbe2ace83
61 changed files with 4512 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.idea/
go.sum
account/
gen_result/
etc/member.yaml

43
build/Dockerfile Normal file
View File

@ -0,0 +1,43 @@
FROM golang:1.22 AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
RUN apt-get update && \
apt-get install git
WORKDIR /build
#
## Download public key for yt.com
#RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan code.30cm.net >> ~/.ssh/known_hosts
#
## Forces the usage of git and ssh key fwded by ssh-agent for yt.com git repos
#RUN git config --global url."git@git.30cm.net:".insteadOf "https://code.30cm.net"
#
## private go packages
#ENV GOPRIVATE=code.30cm.net
ADD ../go.mod .
ADD ../go.sum .
RUN --mount=type=ssh go mod download
ENV FLAG="-s -w -X main.Version=${VERSION} -X main.Built=${BUILT} -X main.GitCommit=${GIT_COMMIT}"
COPY .. .
COPY ../etc /app/etc
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags "$FLAG" \
-o /app/member member.go
FROM gcr.io/distroless/static-debian12
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/Asia/Taipei /usr/share/zoneinfo/Asia/Taipei
ENV TZ Asia/Taipei
WORKDIR /app
COPY --from=builder /app/member /app/member
COPY --from=builder /app/etc /app/etc
CMD ["./member", "-f", "etc/member.yaml"]

10
etc/member-example.yaml Normal file
View File

@ -0,0 +1,10 @@
Name: member.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:
- 127.0.0.1:2379
Key: member.rpc
DB:
DsnString: root:yytt@tcp(127.0.0.1:3306)/ark_member?charset=utf8mb4&parseTime=true&loc=Asia%2FTaipei
Cache:
- Host: 127.0.0.1:7001

View File

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS `account`;

View File

@ -0,0 +1,10 @@
CREATE TABLE `account` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`account` VARCHAR(50) NOT NULL,
`token` VARCHAR(255) NOT NULL,
`platform` INT NOT NULL COMMENT '平台類型 1. ark 2. google',
`create_time` BIGINT NOT NULL DEFAULT 0,
`update_time` BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE INDEX `uk_account` (`account` ASC)
)ENGINE=InnoDB COMMENT='帳號';

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS `account_to_uid`;

View File

@ -0,0 +1,8 @@
CREATE TABLE `account_to_uid` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`account` VARCHAR(50) NOT NULL,
`uid` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `uk_account` (`account` ASC),
INDEX `idx_uid` (`uid` ASC)
)ENGINE=InnoDB COMMENT='帳號轉換表';

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS `user_table`;

View File

@ -0,0 +1,18 @@
CREATE TABLE `user_table` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`verify_type` tinyint DEFAULT 0 NOT NULL COMMENT '驗證類型 0. 異常 1.信箱 2.手機 3. GA 4.不驗證',
`alarm_type` tinyint DEFAULT 0 NOT NULL COMMENT '告警狀態 0. 異常 1. 正常(未告警) 2.系統告警中',
`status` tinyint DEFAULT 0 NOT NULL COMMENT '會員狀態 0. 異常 1. 尚未驗證 2. 啟用 3. 停權中 4. 信箱以驗證 5. 手機以驗證 6. GA 以驗證',
`uid` VARCHAR(255) NOT NULL,
`role_id` VARCHAR(255) NOT NULL DEFAULT '',
`language` VARCHAR(255) NOT NULL DEFAULT '',
`currency` VARCHAR(255) NOT NULL DEFAULT '',
`nick_name` VARCHAR(255) DEFAULT '',
`gender` tinyint DEFAULT 0 NOT NULL COMMENT '0. 不願透露, 1 男 2 女',
`birthday` INT NOT NULL DEFAULT 0,
`create_time` BIGINT NOT NULL DEFAULT 0,
`update_time` BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `idx_create_time` (`create_time` ASC),
UNIQUE INDEX `uk_uid` (`uid` ASC)
)ENGINE=InnoDB COMMENT='使用者資訊';

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS `machine_node`;

View File

@ -0,0 +1,7 @@
CREATE TABLE `machine_node` (
`id` bigint AUTO_INCREMENT NOT NULL COMMENT '流水號',
`create_time` bigint DEFAULT 0 NOT NULL COMMENT '創建時間',
`update_time` bigint DEFAULT 0 NOT NULL COMMENT '更新時間',
`host_name` varchar(200) DEFAULT '' NOT NULL COMMENT 'host name',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='machineID Assigner for Generator';

View File

@ -0,0 +1 @@
DROP DATABASE IF EXISTS `ark_member`;

View File

@ -0,0 +1 @@
CREATE DATABASE IF NOT EXISTS `ark_member`;

View File

@ -0,0 +1,216 @@
syntax = "proto3";
package member;
option go_package="./member";
// ================ enum ================
enum VerifyType {
VERIFY_NONE = 0; //
VERIFY_EMAIL = 1;
VERIFY_PHONE = 2;
VERIFY_GOOGLE = 3;
}
enum AlarmType {
ALARM_NONE = 0; //
ALARM_NOT = 1; //
ALARM_SYSTEM = 2; //
}
enum MemberStatus {
STATUS_NONE = 0; //
STATUS_VERIFY = 1; //
STATUS_COMPLETE = 2; //
STATUS_DISABLE = 3; //
STATUS_EMAIL = 4; //
STATUS_PHONE = 5; //
STATUS_GA = 6; // GA
}
enum Gender {
GENDER_NONE = 0; //
GENDER_MALE = 1; //
GENDER_FEMALE = 2; //
}
// ================ enum ================
// ================ common ================
message Pager {
int64 total =1;
int64 size=2;
int64 index=3;
}
message Response {
BaseResp status=1;
}
message BaseResp {
string code = 1;
string message = 2;
string error = 3;
}
// ================ common ================
// ================ account ================
message CreateLoginUserReq {
string login_id = 1;
int64 platform = 2;
string token = 3;
}
message BindingUserReq {
string uid = 1;
string login_id = 2;
}
message CreateUserInfoReq {
string uid = 1;
VerifyType verify_type = 2;
AlarmType alarm_type = 3;
MemberStatus status = 4;
string role_id = 5;
string language = 6;
string currency = 7;
optional string nick_name = 8;
optional uint32 gender = 9;
optional int64 birthday = 10;
}
message GetAccountInfoResp {
BaseResp status = 1;
CreateLoginUserReq data = 2;
}
// UpdateUserInfoReq
message UpdateUserInfoReq {
string uid = 1;
optional string language = 2;
optional string currency = 3;
optional string nick_name = 4;
optional uint32 gender = 5;
optional int64 birthday = 6;
optional VerifyType verify_type = 7;
optional AlarmType alarm_type = 8;
}
message GetUIDByAccountReq {
string account = 1;
}
message UID {
string uid = 1;
}
message GetUidByAccountResp {
BaseResp status = 1;
UID data = 2;
}
message UpdateTokenReq {
string account = 1;
string token = 2;
}
message GenerateRefreshCodeReq {
string account = 1;
int32 code_type =2;
}
message VerifyCode {
string verify_code = 1;
}
message GenerateRefreshCodeResp {
BaseResp status = 1;
VerifyCode data = 2;
}
message VerifyRefreshCodeReq {
string account = 1;
int32 code_type =2;
string verify_code = 3;
}
message UpdateStatusReq {
string account = 1;
MemberStatus status = 2;
}
message GetUserInfoReq {
string uid = 1;
optional string nick_name =2;
}
message UserInfo {
string uid = 1;
VerifyType verify_type = 2;
AlarmType alarm_type = 3;
MemberStatus status = 4;
string role_id = 5;
string language = 6;
string currency = 7;
optional string nick_name = 8;
optional uint32 gender = 9;
optional int64 birthday = 10;
}
message GetUserInfoResp {
BaseResp status = 1;
UserInfo data = 2;
}
message ListUserInfoReq {
optional string role_id = 1;
optional VerifyType verify_type = 2;
optional AlarmType alarm_type = 3;
optional MemberStatus status = 4;
optional int64 create_start_time = 5;
optional int64 create_end_time = 6;
int64 page_size =7;
int64 page_index=8;
}
message ListUserInfoResp {
BaseResp status = 1;
repeated UserInfo data = 2;
Pager page =3;
}
service Account {
// CreateUserAccount ->
rpc CreateUserAccount(CreateLoginUserReq) returns(Response);
// GetUserAccountInfo
rpc GetUserAccountInfo(GetUIDByAccountReq) returns(GetAccountInfoResp);
// UpdateUserToken
rpc UpdateUserToken(UpdateTokenReq) returns(Response);
// GetUidByAccount UID
rpc GetUidByAccount(GetUIDByAccountReq) returns(GetUidByAccountResp);
// BindAccount -> account bind to UID
rpc BindAccount(BindingUserReq) returns(Response);
// BindUserInfo User Info
rpc BindUserInfo(CreateUserInfoReq) returns(Response);
// UpdateUserInfo User Info
rpc UpdateUserInfo(UpdateUserInfoReq) returns(Response);
// UpdateStatus
rpc UpdateStatus(UpdateStatusReq) returns(Response);
// UpdateStatus
rpc GetUserInfo(GetUserInfoReq) returns(GetUserInfoResp);
// ListMember
rpc ListMember(ListUserInfoReq) returns(ListUserInfoResp);
// GenerateRefreshCode
rpc GenerateRefreshCode(GenerateRefreshCodeReq) returns(GenerateRefreshCodeResp);
// VerifyRefreshCode token
rpc VerifyRefreshCode(VerifyRefreshCodeReq) returns(Response);
}
// ================ account ================

101
go.mod Normal file
View File

@ -0,0 +1,101 @@
module member
go 1.22.3
require (
github.com/bwmarrin/snowflake v0.3.0
github.com/go-playground/validator/v10 v10.22.0
github.com/go-sql-driver/mysql v1.8.1
github.com/golang/mock v1.6.0
github.com/stretchr/testify v1.9.0
github.com/zeromicro/go-zero v1.6.6
go.uber.org/goleak v1.2.1
golang.org/x/crypto v0.24.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
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.3.0 // 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/protobuf v1.5.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
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // 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/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/redis/go-redis/v9 v9.5.3 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.14 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect
go.etcd.io/etcd/client/v3 v3.5.14 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/sdk v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.29.3 // indirect
k8s.io/apimachinery v0.29.4 // indirect
k8s.io/client-go v0.29.3 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

19
internal/config/config.go Executable file
View File

@ -0,0 +1,19 @@
package config
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
zrpc.RpcServerConf
// 加上DB結構體
DB struct {
DsnString string
}
Cache cache.CacheConf
Bcrypt struct {
Cost int
}
}

5
internal/domain/const.go Normal file
View File

@ -0,0 +1,5 @@
package domain
const (
Scope = 10
)

View File

@ -0,0 +1,16 @@
package domain
import "fmt"
type Code int
func (c Code) ToString() string {
return fmt.Sprintf("%d", c)
}
const (
CodeOk = Code(102000)
CodeParamInvalid = Code(304000)
CodeInternalError = Code(305000)
CodeAccountExists = Code(306000)
)

View File

@ -0,0 +1,98 @@
package code
const (
OK uint32 = 0
)
// Scope
const (
Unset uint32 = iota
CloudEPPortalGW
CloudEPMember
)
// Category for general operations: 100 - 4900
const (
_ = iota
CatInput uint32 = iota * 100
CatDB
CatResource
CatGRPC
CatAuth
CatSystem
CatPubSub
)
// CatArk Category for specific app/service: 5000 - 9900
const (
CatArk uint32 = (iota + 50) * 100
)
// Detail - Input 1xx
const (
_ = iota + CatInput
InvalidFormat
NotValidImplementation
InvalidRange
)
// Detail - Database 2xx
const (
_ = iota + CatDB
DBError // general error
DBDataConvert
DBDuplicate
)
// Detail - Resource 3xx
const (
_ = iota + CatResource
ResourceNotFound
InvalidResourceFormat
ResourceAlreadyExist
ResourceInsufficient
InsufficientPermission
InvalidMeasurementID
ResourceExpired
ResourceMigrated
InvalidResourceState
InsufficientQuota
ResourceHasMultiOwner
)
/* Detail - GRPC */
// The GRPC detail code uses Go GRPC's built-in codes.
// Refer to "google.golang.org/grpc/codes" for more detail.
// Detail - Auth 5xx
const (
_ = iota + CatAuth
Unauthorized
AuthExpired
InvalidPosixTime
SigAndPayloadNotMatched
Forbidden
)
// Detail - System 6xx
const (
_ = iota + CatSystem
SystemInternalError
SystemMaintainError
SystemTimeoutError
)
// Detail - PubSub 7xx
const (
_ = iota + CatPubSub
Publish
Consume
MsgSizeTooLarge
)
// Detail - Ark 5xxx
const (
_ = iota + CatArk
ArkInternal
ArkHttp400
)

View File

@ -0,0 +1,13 @@
package code
// CatToStr collects general error messages for each Category
// It is used to send back to API caller
var CatToStr = map[uint32]string{
CatInput: "Invalid Input Data",
CatDB: "Database Error",
CatResource: "Resource Error",
CatGRPC: "Internal Service Communication Error",
CatAuth: "Authentication Error",
CatArk: "Internal Service Communication Error",
CatSystem: "System Error",
}

View File

@ -0,0 +1,441 @@
package error
import (
"errors"
"fmt"
"github.com/zeromicro/go-zero/core/logx"
_ "github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"member/internal/lib/error/code"
"strings"
)
func newErr(scope, detail uint32, msg string) *Err {
cat := detail / 100 * 100
return &Err{
category: cat,
code: detail,
scope: scope,
msg: msg,
}
}
func newBuiltinGRPCErr(scope, detail uint32, msg string) *Err {
return &Err{
category: code.CatGRPC,
code: detail,
scope: scope,
msg: msg,
}
}
// FromError tries to let error as Err
// it supports to unwrap error that has Err
// return nil if failed to transfer
func FromError(err error) *Err {
if err == nil {
return nil
}
var e *Err
if errors.As(err, &e) {
return e
}
return nil
}
// FromCode parses code as following
// Decimal: 120314
// 12 represents Scope
// 03 represents Category
// 14 represents Detail error code
func FromCode(code uint32) *Err {
scope := code / 10000
detail := code % 10000
return &Err{
category: detail / 100 * 100,
code: detail,
scope: scope,
msg: "",
}
}
// FromGRPCError transfer error to Err
// useful for gRPC client
func FromGRPCError(err error) *Err {
s, _ := status.FromError(err)
e := FromCode(uint32(s.Code()))
e.msg = s.Message()
// For GRPC built-in code
if e.Scope() == code.Unset && e.Category() == 0 && e.Code() != code.OK {
e = newBuiltinGRPCErr(Scope, e.Code(), s.Message())
}
return e
}
// Deprecated: check GRPCStatus() in Errs struct
// ToGRPCError returns the status.Status
// Useful to return error in gRPC server
func ToGRPCError(e *Err) error {
return status.New(codes.Code(e.FullCode()), e.Error()).Err()
}
/*** System ***/
// SystemTimeoutError returns Err
func SystemTimeoutError(s ...string) *Err {
return newErr(Scope, code.SystemTimeoutError, fmt.Sprintf("system timeout: %s", strings.Join(s, " ")))
}
// SystemTimeoutErrorL logs error message and returns Err
func SystemTimeoutErrorL(l logx.Logger, s ...string) *Err {
e := SystemTimeoutError(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// SystemInternalError returns Err struct
func SystemInternalError(s ...string) *Err {
return newErr(Scope, code.SystemInternalError, fmt.Sprintf("internal error: %s", strings.Join(s, " ")))
}
// SystemInternalErrorL logs error message and returns Err
func SystemInternalErrorL(l logx.Logger, s ...string) *Err {
e := SystemInternalError(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// SystemMaintainErrorL logs error message and returns Err
func SystemMaintainErrorL(l logx.Logger, s ...string) *Err {
e := SystemMaintainError(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// SystemMaintainError returns Err struct
func SystemMaintainError(s ...string) *Err {
return newErr(Scope, code.SystemMaintainError, fmt.Sprintf("service under maintenance: %s", strings.Join(s, " ")))
}
/*** CatInput ***/
// InvalidFormat returns Err struct
func InvalidFormat(s ...string) *Err {
return newErr(Scope, code.InvalidFormat, fmt.Sprintf("invalid format: %s", strings.Join(s, " ")))
}
// InvalidFormatL logs error message and returns Err
func InvalidFormatL(l logx.Logger, s ...string) *Err {
e := InvalidFormat(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// InvalidRange returns Err struct
func InvalidRange(s ...string) *Err {
return newErr(Scope, code.InvalidRange, fmt.Sprintf("invalid range: %s", strings.Join(s, " ")))
}
// InvalidRangeL logs error message and returns Err
func InvalidRangeL(l logx.Logger, s ...string) *Err {
e := InvalidRange(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// NotValidImplementation returns Err struct
func NotValidImplementation(s ...string) *Err {
return newErr(Scope, code.NotValidImplementation, fmt.Sprintf("not valid implementation: %s", strings.Join(s, " ")))
}
// NotValidImplementationL logs error message and returns Err
func NotValidImplementationL(l logx.Logger, s ...string) *Err {
e := NotValidImplementation(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
/*** CatDB ***/
// DBError returns Err
func DBError(s ...string) *Err {
return newErr(Scope, code.DBError, fmt.Sprintf("db error: %s", strings.Join(s, " ")))
}
// DBErrorL logs error message and returns Err
func DBErrorL(l logx.Logger, s ...string) *Err {
e := DBError(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// DBDataConvert returns Err
func DBDataConvert(s ...string) *Err {
return newErr(Scope, code.DBDataConvert, fmt.Sprintf("data from db convert error: %s", strings.Join(s, " ")))
}
// DBDataConvertL logs error message and returns Err
func DBDataConvertL(l logx.Logger, s ...string) *Err {
e := DBDataConvert(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// DBDuplicate returns Err
func DBDuplicate(s ...string) *Err {
return newErr(Scope, code.DBDuplicate, fmt.Sprintf("data Duplicate key error: %s", strings.Join(s, " ")))
}
// DBDuplicateL logs error message and returns Err
func DBDuplicateL(l logx.Logger, s ...string) *Err {
e := DBDuplicate(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
/*** CatResource ***/
// ResourceNotFound returns Err and logging
func ResourceNotFound(s ...string) *Err {
return newErr(Scope, code.ResourceNotFound, fmt.Sprintf("resource not found: %s", strings.Join(s, " ")))
}
// ResourceNotFoundL logs error message and returns Err
func ResourceNotFoundL(l logx.Logger, s ...string) *Err {
e := ResourceNotFound(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// InvalidResourceFormat returns Err
func InvalidResourceFormat(s ...string) *Err {
return newErr(Scope, code.InvalidResourceFormat, fmt.Sprintf("invalid resource format: %s", strings.Join(s, " ")))
}
// InvalidResourceFormatL logs error message and returns Err
func InvalidResourceFormatL(l logx.Logger, s ...string) *Err {
e := InvalidResourceFormat(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// InvalidResourceState returns status not correct.
// for example: company should be destroy, agent should be no-sensor/fail-install ...
func InvalidResourceState(s ...string) *Err {
return newErr(Scope, code.InvalidResourceState, fmt.Sprintf("invalid resource state: %s", strings.Join(s, " ")))
}
// InvalidResourceStateL logs error message and returns status not correct.
func InvalidResourceStateL(l logx.Logger, s ...string) *Err {
e := InvalidResourceState(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
func ResourceInsufficient(s ...string) *Err {
return newErr(Scope, code.ResourceInsufficient,
fmt.Sprintf("insufficient resource: %s", strings.Join(s, " ")))
}
func ResourceInsufficientL(l logx.Logger, s ...string) *Err {
e := ResourceInsufficient(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// InsufficientPermission returns Err
func InsufficientPermission(s ...string) *Err {
return newErr(Scope, code.InsufficientPermission,
fmt.Sprintf("insufficient permission: %s", strings.Join(s, " ")))
}
// InsufficientPermissionL returns Err and log
func InsufficientPermissionL(l logx.Logger, s ...string) *Err {
e := InsufficientPermission(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// ResourceAlreadyExist returns Err
func ResourceAlreadyExist(s ...string) *Err {
return newErr(Scope, code.ResourceAlreadyExist, fmt.Sprintf("resource already exist: %s", strings.Join(s, " ")))
}
// ResourceAlreadyExistL logs error message and returns Err
func ResourceAlreadyExistL(l logx.Logger, s ...string) *Err {
e := ResourceAlreadyExist(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// InvalidMeasurementID returns Err
func InvalidMeasurementID(s ...string) *Err {
return newErr(Scope, code.InvalidMeasurementID, fmt.Sprintf("missing measurement id: %s", strings.Join(s, " ")))
}
// InvalidMeasurementIDL logs error message and returns Err
func InvalidMeasurementIDL(l logx.Logger, s ...string) *Err {
e := InvalidMeasurementID(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// ResourceExpired returns Err
func ResourceExpired(s ...string) *Err {
return newErr(Scope, code.ResourceExpired, fmt.Sprintf("resource expired: %s", strings.Join(s, " ")))
}
// ResourceExpiredL logs error message and returns Err
func ResourceExpiredL(l logx.Logger, s ...string) *Err {
e := ResourceExpired(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// ResourceMigrated returns Err
func ResourceMigrated(s ...string) *Err {
return newErr(Scope, code.ResourceMigrated, fmt.Sprintf("resource migrated: %s", strings.Join(s, " ")))
}
// ResourceMigratedL logs error message and returns Err
func ResourceMigratedL(l logx.Logger, s ...string) *Err {
e := ResourceMigrated(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// InsufficientQuota returns Err
func InsufficientQuota(s ...string) *Err {
return newErr(Scope, code.InsufficientQuota, fmt.Sprintf("insufficient quota: %s", strings.Join(s, " ")))
}
// InsufficientQuotaL logs error message and returns Err
func InsufficientQuotaL(l logx.Logger, s ...string) *Err {
e := InsufficientQuota(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
/*** CatAuth ***/
// Unauthorized returns Err
func Unauthorized(s ...string) *Err {
return newErr(Scope, code.Unauthorized, fmt.Sprintf("unauthorized: %s", strings.Join(s, " ")))
}
// UnauthorizedL logs error message and returns Err
func UnauthorizedL(l logx.Logger, s ...string) *Err {
e := Unauthorized(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// AuthExpired returns Err
func AuthExpired(s ...string) *Err {
return newErr(Scope, code.AuthExpired, fmt.Sprintf("expired: %s", strings.Join(s, " ")))
}
// AuthExpiredL logs error message and returns Err
func AuthExpiredL(l logx.Logger, s ...string) *Err {
e := AuthExpired(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// InvalidPosixTime returns Err
func InvalidPosixTime(s ...string) *Err {
return newErr(Scope, code.InvalidPosixTime, fmt.Sprintf("invalid posix time: %s", strings.Join(s, " ")))
}
// InvalidPosixTimeL logs error message and returns Err
func InvalidPosixTimeL(l logx.Logger, s ...string) *Err {
e := InvalidPosixTime(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// SigAndPayloadNotMatched returns Err
func SigAndPayloadNotMatched(s ...string) *Err {
return newErr(Scope, code.SigAndPayloadNotMatched, fmt.Sprintf("signature and the payload are not match: %s", strings.Join(s, " ")))
}
// SigAndPayloadNotMatchedL logs error message and returns Err
func SigAndPayloadNotMatchedL(l logx.Logger, s ...string) *Err {
e := SigAndPayloadNotMatched(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// Forbidden returns Err
func Forbidden(s ...string) *Err {
return newErr(Scope, code.Forbidden, fmt.Sprintf("forbidden: %s", strings.Join(s, " ")))
}
// ForbiddenL logs error message and returns Err
func ForbiddenL(l logx.Logger, s ...string) *Err {
e := Forbidden(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// IsAuthUnauthorizedError check the err is unauthorized error
func IsAuthUnauthorizedError(err *Err) bool {
switch err.Code() {
case code.Unauthorized, code.AuthExpired, code.InvalidPosixTime,
code.SigAndPayloadNotMatched, code.Forbidden,
code.InvalidFormat, code.ResourceNotFound:
return true
default:
return false
}
}
/*** CatXBC ***/
// ArkInternal returns Err
func ArkInternal(s ...string) *Err {
return newErr(Scope, code.ArkInternal, fmt.Sprintf("ark internal error: %s", strings.Join(s, " ")))
}
// ArkInternalL logs error message and returns Err
func ArkInternalL(l logx.Logger, s ...string) *Err {
e := ArkInternal(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
/*** CatPubSub ***/
// Publish returns Err
func Publish(s ...string) *Err {
return newErr(Scope, code.Publish, fmt.Sprintf("publish: %s", strings.Join(s, " ")))
}
// PublishL logs error message and returns Err
func PublishL(l logx.Logger, s ...string) *Err {
e := Publish(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}
// Consume returns Err
func Consume(s ...string) *Err {
return newErr(Scope, code.Consume, fmt.Sprintf("consume: %s", strings.Join(s, " ")))
}
// MsgSizeTooLarge returns Err
func MsgSizeTooLarge(s ...string) *Err {
return newErr(Scope, code.MsgSizeTooLarge, fmt.Sprintf("kafka error: %s", strings.Join(s, " ")))
}
// MsgSizeTooLargeL logs error message and returns Err
func MsgSizeTooLargeL(l logx.Logger, s ...string) *Err {
e := MsgSizeTooLarge(s...)
l.WithCallerSkip(1).Error(e.Error())
return e
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,197 @@
package error
import (
"errors"
"fmt"
"member/internal/lib/error/code"
"net/http"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// TODO Error要移到common 包
// Scope global variable should be set by service or module
var Scope = code.Unset
type Err struct {
category uint32
code uint32
scope uint32
msg string
internalErr error
}
// Error is the interface of error
// Getter function of private property "msg"
func (e *Err) Error() string {
if e == nil {
return ""
}
// chain the error string if the internal err exists
var internalErrStr string
if e.internalErr != nil {
internalErrStr = e.internalErr.Error()
}
if e.msg != "" {
if internalErrStr != "" {
return fmt.Sprintf("%s: %s", e.msg, internalErrStr)
}
return e.msg
}
generalErrStr := e.GeneralError()
if internalErrStr != "" {
return fmt.Sprintf("%s: %s", generalErrStr, internalErrStr)
}
return generalErrStr
}
// Category getter function of private property "category"
func (e *Err) Category() uint32 {
if e == nil {
return 0
}
return e.category
}
// Scope getter function of private property "scope"
func (e *Err) Scope() uint32 {
if e == nil {
return code.Unset
}
return e.scope
}
// CodeStr returns the string of error code with zero padding
func (e *Err) CodeStr() string {
if e == nil {
return "00000"
}
if e.Category() == code.CatGRPC {
return fmt.Sprintf("%d%04d", e.Scope(), e.Category()+e.Code())
}
return fmt.Sprintf("%d%04d", e.Scope(), e.Code())
}
// Code getter function of private property "code"
func (e *Err) Code() uint32 {
if e == nil {
return code.OK
}
return e.code
}
func (e *Err) FullCode() uint32 {
if e == nil {
return 0
}
if e.Category() == code.CatGRPC {
return e.Scope()*10000 + e.Category() + e.Code()
}
return e.Scope()*10000 + e.Code()
}
// HTTPStatus returns corresponding HTTP status code
func (e *Err) HTTPStatus() int {
if e == nil || e.Code() == code.OK {
return http.StatusOK
}
// determine status code by code
switch e.Code() {
case code.ResourceInsufficient:
// 400
return http.StatusBadRequest
case code.Unauthorized, code.InsufficientPermission:
// 401
return http.StatusUnauthorized
case code.InsufficientQuota:
// 402
return http.StatusPaymentRequired
case code.InvalidPosixTime, code.Forbidden:
// 403
return http.StatusForbidden
case code.ResourceNotFound:
// 404
return http.StatusNotFound
case code.ResourceAlreadyExist, code.InvalidResourceState:
// 409
return http.StatusConflict
case code.NotValidImplementation:
// 501
return http.StatusNotImplemented
default:
}
// determine status code by category
switch e.Category() {
case code.CatInput:
return http.StatusBadRequest
default:
// return status code 500 if none of the condition is met
return http.StatusInternalServerError
}
}
// GeneralError transform category level error message
// It's the general error message for customer/API caller
func (e *Err) GeneralError() string {
if e == nil {
return ""
}
errStr, ok := code.CatToStr[e.Category()]
if !ok {
return ""
}
return errStr
}
// Is called when performing errors.Is().
// DO NOT USE THIS FUNCTION DIRECTLY unless you are very certain about what you're doing.
// Use errors.Is instead.
// This function compares if two error variables are both *Err, and have the same code (without checking the wrapped internal error)
func (e *Err) Is(f error) bool {
var err *Err
ok := errors.As(f, &err)
if !ok {
return false
}
return e.Code() == err.Code()
}
// Unwrap returns the underlying error
// The result of unwrapping an error may itself have an Unwrap method;
// we call the sequence of errors produced by repeated unwrapping the error chain.
func (e *Err) Unwrap() error {
if e == nil {
return nil
}
return e.internalErr
}
// Wrap sets the internal error to Err struct
func (e *Err) Wrap(internalErr error) *Err {
if e != nil {
e.internalErr = internalErr
}
return e
}
func (e *Err) GRPCStatus() *status.Status {
if e == nil {
return status.New(codes.OK, "")
}
return status.New(codes.Code(e.FullCode()), e.Error())
}

View File

@ -0,0 +1,296 @@
package error
import (
"errors"
"fmt"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"member/internal/lib/error/code"
"net/http"
"testing"
)
func TestCode_GivenNilReceiver_CodeReturnOK_CodeStrReturns00000(t *testing.T) {
// setup
var e *Err = nil
// act & assert
assert.Equal(t, code.OK, e.Code())
assert.Equal(t, "00000", e.CodeStr())
assert.Equal(t, "", e.Error())
}
func TestCode_GivenScope99DetailCode6687_ShouldReturn996687(t *testing.T) {
// setup
e := Err{scope: 99, code: 6687}
// act & assert
assert.Equal(t, uint32(6687), e.Code())
assert.Equal(t, "996687", e.CodeStr())
}
func TestCode_GivenScope0DetailCode87_ShouldReturn87(t *testing.T) {
// setup
e := Err{scope: 0, code: 87}
// act & assert
assert.Equal(t, uint32(87), e.Code())
assert.Equal(t, "00087", e.CodeStr())
}
func TestFromCode_Given870005_ShouldHasScope87_Cat0_Detail5(t *testing.T) {
// setup
e := FromCode(870005)
// assert
assert.Equal(t, uint32(87), e.Scope())
assert.Equal(t, uint32(0), e.Category())
assert.Equal(t, uint32(5), e.Code())
assert.Equal(t, "", e.Error())
}
func TestFromCode_Given0_ShouldHasScope0_Cat0_Detail0(t *testing.T) {
// setup
e := FromCode(0)
// assert
assert.Equal(t, uint32(0), e.Scope())
assert.Equal(t, uint32(0), e.Category())
assert.Equal(t, uint32(0), e.Code())
assert.Equal(t, "", e.Error())
}
func TestFromCode_Given9105_ShouldHasScope0_Cat9100_Detail9105(t *testing.T) {
// setup
e := FromCode(9105)
// assert
assert.Equal(t, uint32(0), e.Scope())
assert.Equal(t, uint32(9100), e.Category())
assert.Equal(t, uint32(9105), e.Code())
assert.Equal(t, "", e.Error())
}
func TestErr_ShouldImplementErrorFunction(t *testing.T) {
// setup a func return error
f := func() error { return InvalidFormat("fake field") }
// act
err := f()
// assert
assert.NotNil(t, err)
assert.Contains(t, fmt.Sprint(err), "fake field") // can be printed
}
func TestGeneralError_GivenNilErr_ShouldReturnEmptyString(t *testing.T) {
// setup
var e *Err = nil
// act & assert
assert.Equal(t, "", e.GeneralError())
}
func TestGeneralError_GivenNotExistCat_ShouldReturnEmptyString(t *testing.T) {
// setup
e := Err{category: 123456}
// act & assert
assert.Equal(t, "", e.GeneralError())
}
func TestGeneralError_GivenCatDB_ShouldReturnDBError(t *testing.T) {
// setup
e := Err{category: code.CatDB}
catErrStr := code.CatToStr[code.CatDB]
// act & assert
assert.Equal(t, catErrStr, e.GeneralError())
}
func TestError_GivenEmptyMsg_ShouldReturnCatGeneralErrorMessage(t *testing.T) {
// setup
e := Err{category: code.CatDB, msg: ""}
// act
errMsg := e.Error()
// assert
assert.Equal(t, code.CatToStr[code.CatDB], errMsg)
}
func TestError_GivenMsg_ShouldReturnGiveMsg(t *testing.T) {
// setup
e := Err{msg: "FAKE"}
// act
errMsg := e.Error()
// assert
assert.Equal(t, "FAKE", errMsg)
}
func TestIs_GivenNilErr_ShouldReturnFalse(t *testing.T) {
var nilErrs *Err
// act
result := errors.Is(nilErrs, DBError())
result2 := errors.Is(DBError(), nilErrs)
// assert
assert.False(t, result)
assert.False(t, result2)
}
func TestIs_GivenNil_ShouldReturnFalse(t *testing.T) {
// act
result := errors.Is(nil, DBError())
result2 := errors.Is(DBError(), nil)
// assert
assert.False(t, result)
assert.False(t, result2)
}
func TestIs_GivenNilReceiver_ShouldReturnCorrectResult(t *testing.T) {
var nilErr *Err = nil
// test 1: nilErr != DBError
var dbErr error = DBError("fake db error")
assert.False(t, nilErr.Is(dbErr))
// test 2: nilErr != nil error
var nilError error
assert.False(t, nilErr.Is(nilError))
// test 3: nilErr == another nilErr
var nilErr2 *Err = nil
assert.True(t, nilErr.Is(nilErr2))
}
func TestIs_GivenDBError_ShouldReturnTrue(t *testing.T) {
// setup
dbErr := DBError("fake db error")
// act
result := errors.Is(dbErr, DBError("not care"))
result2 := errors.Is(DBError(), dbErr)
// assert
assert.True(t, result)
assert.True(t, result2)
}
func TestIs_GivenDBErrorAssignToErrorType_ShouldReturnTrue(t *testing.T) {
// setup
var dbErr error = DBError("fake db error")
// act
result := errors.Is(dbErr, DBError("not care"))
result2 := errors.Is(DBError(), dbErr)
// assert
assert.True(t, result)
assert.True(t, result2)
}
func TestWrap_GivenNilErr_ShouldNoPanic(t *testing.T) {
// act & assert
assert.NotPanics(t, func() {
var e *Err = nil
_ = e.Wrap(fmt.Errorf("test"))
})
}
func TestWrap_GivenErrorToWrap_ShouldReturnErrorWithWrappedError(t *testing.T) {
// act & assert
wrappedErr := fmt.Errorf("test")
wrappingErr := SystemInternalError("WrappingError").Wrap(wrappedErr)
unWrappedErr := wrappingErr.Unwrap()
assert.Equal(t, wrappedErr, unWrappedErr)
}
func TestUnwrap_GivenNilErr_ShouldReturnNil(t *testing.T) {
var e *Err = nil
internalErr := e.Unwrap()
assert.Nil(t, internalErr)
}
func TestErrorsIs_GivenNilErr_ShouldReturnFalse(t *testing.T) {
var e *Err = nil
assert.False(t, errors.Is(e, fmt.Errorf("test")))
}
func TestErrorsAs_GivenNilErr_ShouldReturnFalse(t *testing.T) {
var internalErr *testErr
var e *Err = nil
assert.False(t, errors.As(e, &internalErr))
}
func TestGRPCStatus(t *testing.T) {
// setup table driven tests
tests := []struct {
name string
given *Err
expect *status.Status
expectConvert error
}{
{
"nil errs.Err",
nil,
status.New(codes.OK, ""),
nil,
},
{
"InvalidFormat Err",
InvalidFormat("fake"),
status.New(codes.Code(101), "invalid format: fake"),
status.New(codes.Code(101), "invalid format: fake").Err(),
},
}
// act & assert
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := test.given.GRPCStatus()
assert.Equal(t, test.expect.Code(), s.Code())
assert.Equal(t, test.expect.Message(), s.Message())
assert.Equal(t, test.expectConvert, status.Convert(test.given).Err())
})
}
}
func TestErr_HTTPStatus(t *testing.T) {
tests := []struct {
name string
err *Err
want int
}{
{name: "nil error", err: nil, want: http.StatusOK},
{name: "invalid measurement id", err: &Err{category: code.CatResource, code: code.InvalidMeasurementID}, want: http.StatusInternalServerError},
{name: "resource already exists", err: &Err{category: code.CatResource, code: code.ResourceAlreadyExist}, want: http.StatusConflict},
{name: "invalid resource state", err: &Err{category: code.CatResource, code: code.InvalidResourceState}, want: http.StatusConflict},
{name: "invalid posix time", err: &Err{category: code.CatAuth, code: code.InvalidPosixTime}, want: http.StatusForbidden},
{name: "unauthorized", err: &Err{category: code.CatAuth, code: code.Unauthorized}, want: http.StatusUnauthorized},
{name: "db error", err: &Err{category: code.CatDB, code: code.DBError}, want: http.StatusInternalServerError},
{name: "insufficient permission", err: &Err{category: code.CatResource, code: code.InsufficientPermission}, want: http.StatusUnauthorized},
{name: "resource insufficient", err: &Err{category: code.CatResource, code: code.ResourceInsufficient}, want: http.StatusBadRequest},
{name: "invalid format", err: &Err{category: code.CatInput, code: code.InvalidFormat}, want: http.StatusBadRequest},
{name: "resource not found", err: &Err{code: code.ResourceNotFound}, want: http.StatusNotFound},
{name: "ok", err: &Err{code: code.OK}, want: http.StatusOK},
{name: "not valid implementation", err: &Err{category: code.CatInput, code: code.NotValidImplementation}, want: http.StatusNotImplemented},
{name: "forbidden", err: &Err{category: code.CatAuth, code: code.Forbidden}, want: http.StatusForbidden},
{name: "insufficient quota", err: &Err{category: code.CatResource, code: code.InsufficientQuota}, want: http.StatusPaymentRequired},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// act
got := tt.err.HTTPStatus()
// assert
assert.Equal(t, tt.want, got)
})
}
}

View File

@ -0,0 +1,27 @@
package middleware
import (
"context"
"errors"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc"
ers "member/internal/lib/error"
"time"
)
const defaultTimeout = 30 * time.Second
func TimeoutMiddleware(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
newCtx, cancelCtx := context.WithTimeout(ctx, defaultTimeout)
defer func() {
cancelCtx()
if errors.Is(newCtx.Err(), context.DeadlineExceeded) {
err = ers.SystemTimeoutError(info.FullMethod)
logx.Errorf("Method: %s, request %v, timeout: %d", info.FullMethod, req, defaultTimeout)
}
}()
return handler(ctx, req)
}

View File

@ -0,0 +1,24 @@
package required
import "github.com/go-playground/validator/v10"
// ValidateAll TODO 要移到common 包
func ValidateAll(validate *validator.Validate, obj any) error {
err := validate.Struct(obj)
if err != nil {
return err
}
return nil
}
func MustValidator(option ...Option) *validator.Validate {
// TODO Validator 要抽出來
v := validator.New()
err := BindToValidator(v, option...)
if err != nil {
// log
}
return v
}

View File

@ -0,0 +1,41 @@
package required
import (
"fmt"
"regexp"
"github.com/go-playground/validator/v10"
)
type Option struct {
ValidatorName string
ValidatorFunc func(fl validator.FieldLevel) bool
}
func BindToValidator(v *validator.Validate, opts ...Option) error {
for _, item := range opts {
err := v.RegisterValidation(item.ValidatorName, item.ValidatorFunc)
if err != nil {
return fmt.Errorf("failed to register validator : %w", err)
}
}
return nil
}
// WithAccount 創建一個新的 Option 結構,包含自定義的驗證函數,用於驗證 email 和台灣的手機號碼格式
func WithAccount(tagName string) Option {
return Option{
ValidatorName: tagName,
ValidatorFunc: func(fl validator.FieldLevel) bool {
value := fl.Field().String()
emailRegex := `^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`
phoneRegex := `^(\+886|0)?9\d{8}$`
emailMatch, _ := regexp.MatchString(emailRegex, value)
phoneMatch, _ := regexp.MatchString(phoneRegex, value)
return emailMatch || phoneMatch
},
}
}

View File

@ -0,0 +1,18 @@
package snowflake
import (
"fmt"
"time"
)
type NewNodeError struct {
machineNodeID int64
startTime time.Time
Err error
}
func (e *NewNodeError) Error() string {
return fmt.Sprintf("new node fail machineNodeID: %d, startTime: %s, err: %v",
e.machineNodeID, e.startTime, e.Err)
}

View File

@ -0,0 +1,54 @@
package snowflake
import (
"fmt"
"testing"
"time"
)
func TestNewNodeError_Error(t *testing.T) {
startTime, err := time.Parse(time.DateOnly, "2023-07-20")
if err != nil {
t.Error(err)
}
t.Parallel()
type fields struct {
machineNodeID int64
startTime time.Time
Err error
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "success",
fields: fields{
machineNodeID: 1,
startTime: startTime,
Err: nil,
},
want: fmt.Sprintf("new node fail machineNodeID: %d, startTime: %s, err: %v",
1, "2023-07-20 00:00:00 +0000 UTC", nil),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
e := &NewNodeError{
machineNodeID: tt.fields.machineNodeID,
startTime: tt.fields.startTime,
Err: tt.fields.Err,
}
if got := e.Error(); got != tt.want {
t.Errorf("Error() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,8 @@
###
# snowflake
```go
import "yt.com/backend/common.git/snowflake"
```
### 量級超過1024 再來做解決

View File

@ -0,0 +1,81 @@
package snowflake
import (
"fmt"
"sync"
"time"
"github.com/bwmarrin/snowflake"
)
var mu sync.Mutex
// Snowflake provides a way to NewNode for Generate UID.
type Snowflake struct {
machineNodeID int64
startTime time.Time
}
// Option is the options type to configure Snowflake.
type Option func(*Snowflake)
// New returns a new Snowflake instance with the provided options.
func New(opts ...Option) *Snowflake {
s := &Snowflake{
// default machine 1
machineNodeID: 1,
}
for _, opt := range opts {
opt(s)
}
return s
}
// WithMachineNodeID adds machineID total 10bit = 1024 machine number.
func WithMachineNodeID(machineNodeID int64) Option {
return func(snowflake *Snowflake) {
snowflake.machineNodeID = machineNodeID
}
}
// WithStartTime adds snowflake start timestamp in milliseconds.
func WithStartTime(startTime time.Time) Option {
return func(snowflake *Snowflake) {
snowflake.startTime = startTime
}
}
// GetNowDate return nowTodayDate e.g. 2023-07-20 00:00:00 +0000 UTC.
func GetNowDate() (time.Time, error) {
startTime := time.Now().UTC().Format(time.DateOnly)
st, err := time.Parse(time.DateOnly, startTime)
if err != nil {
return time.Time{}, fmt.Errorf("time.Parse failed :%w", err)
}
return st, nil
}
// NewNode return snowflake node use Generate UID.
func (s *Snowflake) NewNode() (*snowflake.Node, error) {
mu.Lock()
defer mu.Unlock()
snowflake.Epoch = s.startTime.UnixMilli()
node, err := snowflake.NewNode(s.machineNodeID)
if err != nil {
return nil, fmt.Errorf("snowflake.NewNode, failed :%w",
&NewNodeError{
machineNodeID: s.machineNodeID,
startTime: s.startTime,
Err: err,
})
}
return node, nil
}

View File

@ -0,0 +1,148 @@
package snowflake
import (
"flag"
"os"
"reflect"
"testing"
"time"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
leak := flag.Bool("leak", false, "use leak detector")
flag.Parse()
if *leak {
goleak.VerifyTestMain(m)
return
}
os.Exit(m.Run())
}
func TestSnowflake(t *testing.T) {
st, err := GetNowDate()
if err != nil {
t.Error(err)
}
t.Parallel()
type args struct {
machineNodeID int64
startTime time.Time
}
tests := []struct {
name string
args args
want *Snowflake
wantDeepEqualErr bool
wantNewNodeErr bool
}{
{
name: "success",
args: args{
machineNodeID: 10,
startTime: st,
},
want: &Snowflake{
machineNodeID: 10,
startTime: st,
},
wantDeepEqualErr: false,
wantNewNodeErr: false,
},
{
name: "failed machine node ID negative number",
args: args{
machineNodeID: -1,
startTime: time.Time{},
},
want: &Snowflake{
machineNodeID: -1,
startTime: time.Time{},
},
wantDeepEqualErr: false,
wantNewNodeErr: true,
},
{
name: "failed snowflake struct field by machine node ID",
args: args{
machineNodeID: 10,
startTime: st,
},
want: &Snowflake{
machineNodeID: 2,
startTime: st,
},
wantDeepEqualErr: true,
wantNewNodeErr: false,
},
{
name: "failed snowflake struct field by startTime",
args: args{
machineNodeID: 2,
startTime: st,
},
want: &Snowflake{
machineNodeID: 2,
startTime: time.Time{},
},
wantDeepEqualErr: true,
wantNewNodeErr: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := New(
WithMachineNodeID(tt.args.machineNodeID),
WithStartTime(tt.args.startTime),
)
if !reflect.DeepEqual(got, tt.want) != tt.wantDeepEqualErr {
t.Errorf("Snowflake.New() = %v, want %v", got, tt.want)
}
node, err := got.NewNode()
if (err != nil) != tt.wantNewNodeErr {
t.Errorf("NewNode() = %v, want %v", err != nil, tt.wantNewNodeErr)
}
if err == nil {
id := node.Generate().Int64()
if id <= 0 {
t.Errorf("node.Generate().Int64() = %v, want %s", id, "id > 0")
}
}
})
}
}
func BenchmarkSnowflake(b *testing.B) {
st, err := GetNowDate()
if err != nil {
b.Error(err)
}
snowflake := New(
WithMachineNodeID(1),
WithStartTime(st),
)
node, err := snowflake.NewNode()
if err != nil {
b.Error(err)
}
for i := 0; i < b.N; i++ {
node.Generate().Int64()
}
}

View File

@ -0,0 +1,73 @@
package logic
import (
"context"
"fmt"
"member/internal/domain"
ers "member/internal/lib/error"
"member/internal/lib/required"
"member/internal/model"
"strconv"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type BindAccountLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewBindAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindAccountLogic {
return &BindAccountLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
type bindLoginUserReq struct {
Account string `json:"account" validate:"account"`
UID string `json:"uid" `
}
// BindAccount 綁定帳號 -> account bind to UID
func (l *BindAccountLogic) BindAccount(in *member.BindingUserReq) (*member.Response, error) {
// 驗證資料
err := required.ValidateAll(l.svcCtx.Validate, &bindLoginUserReq{
Account: in.GetLoginId(),
})
if err != nil {
return nil, ers.InvalidFormat(err.Error())
}
uid := in.GetUid()
// 有UID 綁看看沒帶UID 近來,確認沒重複就直接綁一個給他
if in.GetUid() == "" {
uid = strconv.FormatInt(int64(l.svcCtx.SnackFlowGen.Generate()), 10)
}
// 先確定有這個Account
_, err = l.svcCtx.AccountModel.FindOneByAccount(l.ctx, in.GetLoginId())
if err != nil {
return nil, ers.ResourceNotFound(fmt.Sprintf("failed to get account : %s ", in.GetLoginId()))
}
_, err = l.svcCtx.AccountToUidModel.Insert(l.ctx, &model.AccountToUid{
Account: in.LoginId,
Uid: uid,
})
if err != nil {
return nil, ers.DBError(err.Error())
}
return &member.Response{
Status: &member.BaseResp{
Code: domain.CodeOk.ToString(),
Message: "success",
Error: "",
},
}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type BindUserInfoLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewBindUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindUserInfoLogic {
return &BindUserInfoLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// BindUserInfo 初次,綁定 User Info
func (l *BindUserInfoLogic) BindUserInfo(in *member.CreateUserInfoReq) (*member.Response, error) {
// todo: add your logic here and delete this line
return &member.Response{}, nil
}

View File

@ -0,0 +1,76 @@
package logic
import (
"context"
"errors"
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/zeromicro/go-zero/core/logx"
"member/gen_result/pb/member"
ers "member/internal/lib/error"
"member/internal/lib/required"
"member/internal/model"
"member/internal/svc"
"member/internal/utils"
"time"
)
type CreateUserAccountLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCreateUserAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateUserAccountLogic {
return &CreateUserAccountLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
type createLoginUserReq struct {
LoginId string `json:"login_id" validate:"account"`
Platform int64 `json:"platform" validate:"required,oneof=1 2 3"`
Token string `json:"token" validate:"required"`
}
// CreateUserAccount 建立帳號與密碼 -> 可登入,但可不可以做其他事情看業務流程,也可以只註冊就好
func (l *CreateUserAccountLogic) CreateUserAccount(in *member.CreateLoginUserReq) (*member.Response, error) {
// 驗證資料
err := required.ValidateAll(l.svcCtx.Validate, &createLoginUserReq{
LoginId: in.GetLoginId(),
Platform: in.GetPlatform(),
Token: in.GetToken(),
})
if err != nil {
return nil, ers.InvalidFormat(err.Error())
}
token, err := utils.HashPassword(in.GetToken(), l.svcCtx.Config.Bcrypt.Cost)
if err != nil {
return nil, ers.ArkInternal(fmt.Sprintf("failed to encrypt err: %v", err.Error()))
}
// 新增進去
now := time.Now().UTC().Unix()
_, err = l.svcCtx.AccountModel.Insert(l.ctx, &model.Account{
Account: in.LoginId,
Token: token,
Platform: in.Platform,
CreateTime: now,
UpdateTime: now,
})
if err != nil {
// 新增進去
var mysqlErr *mysql.MySQLError
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
// 處理重複條目錯誤
return nil, ers.DBDuplicate(in.LoginId)
}
return nil, ers.DBError(err.Error())
}
return nil, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GenerateRefreshCodeLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGenerateRefreshCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateRefreshCodeLogic {
return &GenerateRefreshCodeLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// GenerateRefreshCode 這個帳號驗證碼(十分鐘),通用的
func (l *GenerateRefreshCodeLogic) GenerateRefreshCode(in *member.GenerateRefreshCodeReq) (*member.GenerateRefreshCodeResp, error) {
// todo: add your logic here and delete this line
return &member.GenerateRefreshCodeResp{}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUidByAccountLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetUidByAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUidByAccountLogic {
return &GetUidByAccountLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// GetUidByAccount 用帳號換取 UID
func (l *GetUidByAccountLogic) GetUidByAccount(in *member.GetUIDByAccountReq) (*member.GetUidByAccountResp, error) {
// todo: add your logic here and delete this line
return &member.GetUidByAccountResp{}, nil
}

View File

@ -0,0 +1,58 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/domain"
ers "member/internal/lib/error"
"member/internal/lib/required"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUserAccountInfoLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetUserAccountInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserAccountInfoLogic {
return &GetUserAccountInfoLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
type getUserAccountReq struct {
LoginId string `json:"login_id" validate:"account"`
}
// GetUserAccountInfo 取得帳號密碼資料
func (l *GetUserAccountInfoLogic) GetUserAccountInfo(in *member.GetUIDByAccountReq) (*member.GetAccountInfoResp, error) {
// 驗證輸入資料
err := required.ValidateAll(l.svcCtx.Validate, &getUserAccountReq{
LoginId: in.GetAccount(),
})
if err != nil {
return nil, ers.InvalidFormat(err.Error())
}
account, err := l.svcCtx.AccountModel.FindOneByAccount(l.ctx, in.GetAccount())
if err != nil {
return nil, ers.DBError(err.Error())
}
return &member.GetAccountInfoResp{
Status: &member.BaseResp{
Code: domain.CodeOk.ToString(),
},
Data: &member.CreateLoginUserReq{
LoginId: account.Account,
Token: account.Token,
Platform: account.Platform,
},
}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUserInfoLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserInfoLogic {
return &GetUserInfoLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// UpdateStatus 取得會員資訊
func (l *GetUserInfoLogic) GetUserInfo(in *member.GetUserInfoReq) (*member.GetUserInfoResp, error) {
// todo: add your logic here and delete this line
return &member.GetUserInfoResp{}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type ListMemberLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListMemberLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListMemberLogic {
return &ListMemberLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// ListMember 取得會員列表
func (l *ListMemberLogic) ListMember(in *member.ListUserInfoReq) (*member.ListUserInfoResp, error) {
// todo: add your logic here and delete this line
return &member.ListUserInfoResp{}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateStatusLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateStatusLogic {
return &UpdateStatusLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// UpdateStatus 修改狀態
func (l *UpdateStatusLogic) UpdateStatus(in *member.UpdateStatusReq) (*member.Response, error) {
// todo: add your logic here and delete this line
return &member.Response{}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateUserInfoLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserInfoLogic {
return &UpdateUserInfoLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// UpdateUserInfo 更新 User Info
func (l *UpdateUserInfoLogic) UpdateUserInfo(in *member.UpdateUserInfoReq) (*member.Response, error) {
// todo: add your logic here and delete this line
return &member.Response{}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateUserTokenLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateUserTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserTokenLogic {
return &UpdateUserTokenLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// UpdateUserToken 更新密碼
func (l *UpdateUserTokenLogic) UpdateUserToken(in *member.UpdateTokenReq) (*member.Response, error) {
// todo: add your logic here and delete this line
return &member.Response{}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"member/gen_result/pb/member"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type VerifyRefreshCodeLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewVerifyRefreshCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VerifyRefreshCodeLogic {
return &VerifyRefreshCodeLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// VerifyRefreshCode 驗證忘記密碼 token
func (l *VerifyRefreshCodeLogic) VerifyRefreshCode(in *member.VerifyRefreshCodeReq) (*member.Response, error) {
// todo: add your logic here and delete this line
return &member.Response{}, nil
}

27
internal/model/account_model.go Executable file
View File

@ -0,0 +1,27 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ AccountModel = (*customAccountModel)(nil)
type (
// AccountModel is an interface to be customized, add more methods here,
// and implement the added methods in customAccountModel.
AccountModel interface {
accountModel
}
customAccountModel struct {
*defaultAccountModel
}
)
// NewAccountModel returns a model for the database table.
func NewAccountModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) AccountModel {
return &customAccountModel{
defaultAccountModel: newAccountModel(conn, c, opts...),
}
}

View File

@ -0,0 +1,154 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
accountFieldNames = builder.RawFieldNames(&Account{})
accountRows = strings.Join(accountFieldNames, ",")
accountRowsExpectAutoSet = strings.Join(stringx.Remove(accountFieldNames, "`id`"), ",")
accountRowsWithPlaceHolder = strings.Join(stringx.Remove(accountFieldNames, "`id`"), "=?,") + "=?"
cacheAccountIdPrefix = "cache:account:id:"
cacheAccountAccountPrefix = "cache:account:account:"
)
type (
accountModel interface {
Insert(ctx context.Context, data *Account) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*Account, error)
FindOneByAccount(ctx context.Context, account string) (*Account, error)
Update(ctx context.Context, data *Account) error
Delete(ctx context.Context, id int64) error
}
defaultAccountModel struct {
sqlc.CachedConn
table string
}
Account struct {
Id int64 `db:"id"`
Account string `db:"account"`
Token string `db:"token"`
Platform int64 `db:"platform"` // 平台類型 1. ark 2. google
CreateTime int64 `db:"create_time"`
UpdateTime int64 `db:"update_time"`
}
)
func newAccountModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultAccountModel {
return &defaultAccountModel{
CachedConn: sqlc.NewConn(conn, c, opts...),
table: "`account`",
}
}
func (m *defaultAccountModel) withSession(session sqlx.Session) *defaultAccountModel {
return &defaultAccountModel{
CachedConn: m.CachedConn.WithSession(session),
table: "`account`",
}
}
func (m *defaultAccountModel) Delete(ctx context.Context, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
accountAccountKey := fmt.Sprintf("%s%v", cacheAccountAccountPrefix, data.Account)
accountIdKey := fmt.Sprintf("%s%v", cacheAccountIdPrefix, id)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, accountAccountKey, accountIdKey)
return err
}
func (m *defaultAccountModel) FindOne(ctx context.Context, id int64) (*Account, error) {
accountIdKey := fmt.Sprintf("%s%v", cacheAccountIdPrefix, id)
var resp Account
err := m.QueryRowCtx(ctx, &resp, accountIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", accountRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultAccountModel) FindOneByAccount(ctx context.Context, account string) (*Account, error) {
accountAccountKey := fmt.Sprintf("%s%v", cacheAccountAccountPrefix, account)
var resp Account
err := m.QueryRowIndexCtx(ctx, &resp, accountAccountKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
query := fmt.Sprintf("select %s from %s where `account` = ? limit 1", accountRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, account); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultAccountModel) Insert(ctx context.Context, data *Account) (sql.Result, error) {
accountAccountKey := fmt.Sprintf("%s%v", cacheAccountAccountPrefix, data.Account)
accountIdKey := fmt.Sprintf("%s%v", cacheAccountIdPrefix, data.Id)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?)", m.table, accountRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.Account, data.Token, data.Platform, data.CreateTime, data.UpdateTime)
}, accountAccountKey, accountIdKey)
return ret, err
}
func (m *defaultAccountModel) Update(ctx context.Context, newData *Account) error {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
accountAccountKey := fmt.Sprintf("%s%v", cacheAccountAccountPrefix, data.Account)
accountIdKey := fmt.Sprintf("%s%v", cacheAccountIdPrefix, data.Id)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, accountRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.Account, newData.Token, newData.Platform, newData.CreateTime, newData.UpdateTime, newData.Id)
}, accountAccountKey, accountIdKey)
return err
}
func (m *defaultAccountModel) formatPrimary(primary any) string {
return fmt.Sprintf("%s%v", cacheAccountIdPrefix, primary)
}
func (m *defaultAccountModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", accountRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultAccountModel) tableName() string {
return m.table
}

View File

@ -0,0 +1,31 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ AccountToUidModel = (*customAccountToUidModel)(nil)
var (
cacheAccountPrefix = "cache:accountToUid:account:"
)
type (
// AccountToUidModel is an interface to be customized, add more methods here,
// and implement the added methods in customAccountToUidModel.
AccountToUidModel interface {
accountToUidModel
}
customAccountToUidModel struct {
*defaultAccountToUidModel
}
)
// NewAccountToUidModel returns a model for the database table.
func NewAccountToUidModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) AccountToUidModel {
return &customAccountToUidModel{
defaultAccountToUidModel: newAccountToUidModel(conn, c, opts...),
}
}

View File

@ -0,0 +1,151 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
accountToUidFieldNames = builder.RawFieldNames(&AccountToUid{})
accountToUidRows = strings.Join(accountToUidFieldNames, ",")
accountToUidRowsExpectAutoSet = strings.Join(stringx.Remove(accountToUidFieldNames, "`id`"), ",")
accountToUidRowsWithPlaceHolder = strings.Join(stringx.Remove(accountToUidFieldNames, "`id`"), "=?,") + "=?"
cacheAccountToUidIdPrefix = "cache:accountToUid:id:"
cacheAccountToUidAccountPrefix = "cache:accountToUid:account:"
)
type (
accountToUidModel interface {
Insert(ctx context.Context, data *AccountToUid) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*AccountToUid, error)
FindOneByAccount(ctx context.Context, account string) (*AccountToUid, error)
Update(ctx context.Context, data *AccountToUid) error
Delete(ctx context.Context, id int64) error
}
defaultAccountToUidModel struct {
sqlc.CachedConn
table string
}
AccountToUid struct {
Id int64 `db:"id"`
Account string `db:"account"`
Uid string `db:"uid"`
}
)
func newAccountToUidModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultAccountToUidModel {
return &defaultAccountToUidModel{
CachedConn: sqlc.NewConn(conn, c, opts...),
table: "`account_to_uid`",
}
}
func (m *defaultAccountToUidModel) withSession(session sqlx.Session) *defaultAccountToUidModel {
return &defaultAccountToUidModel{
CachedConn: m.CachedConn.WithSession(session),
table: "`account_to_uid`",
}
}
func (m *defaultAccountToUidModel) Delete(ctx context.Context, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
accountToUidAccountKey := fmt.Sprintf("%s%v", cacheAccountToUidAccountPrefix, data.Account)
accountToUidIdKey := fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, id)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, accountToUidAccountKey, accountToUidIdKey)
return err
}
func (m *defaultAccountToUidModel) FindOne(ctx context.Context, id int64) (*AccountToUid, error) {
accountToUidIdKey := fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, id)
var resp AccountToUid
err := m.QueryRowCtx(ctx, &resp, accountToUidIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", accountToUidRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultAccountToUidModel) FindOneByAccount(ctx context.Context, account string) (*AccountToUid, error) {
accountToUidAccountKey := fmt.Sprintf("%s%v", cacheAccountToUidAccountPrefix, account)
var resp AccountToUid
err := m.QueryRowIndexCtx(ctx, &resp, accountToUidAccountKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
query := fmt.Sprintf("select %s from %s where `account` = ? limit 1", accountToUidRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, account); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultAccountToUidModel) Insert(ctx context.Context, data *AccountToUid) (sql.Result, error) {
accountToUidAccountKey := fmt.Sprintf("%s%v", cacheAccountToUidAccountPrefix, data.Account)
accountToUidIdKey := fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, data.Id)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?)", m.table, accountToUidRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.Account, data.Uid)
}, accountToUidAccountKey, accountToUidIdKey)
return ret, err
}
func (m *defaultAccountToUidModel) Update(ctx context.Context, newData *AccountToUid) error {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
accountToUidAccountKey := fmt.Sprintf("%s%v", cacheAccountToUidAccountPrefix, data.Account)
accountToUidIdKey := fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, data.Id)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, accountToUidRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.Account, newData.Uid, newData.Id)
}, accountToUidAccountKey, accountToUidIdKey)
return err
}
func (m *defaultAccountToUidModel) formatPrimary(primary any) string {
return fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, primary)
}
func (m *defaultAccountToUidModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", accountToUidRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultAccountToUidModel) tableName() string {
return m.table
}

View File

@ -0,0 +1,48 @@
package model
import (
"context"
"fmt"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ MachineNodeModel = (*customMachineNodeModel)(nil)
type (
// MachineNodeModel is an interface to be customized, add more methods here,
// and implement the added methods in customMachineNodeModel.
MachineNodeModel interface {
machineNodeModel
FindOneByHostName(ctx context.Context, hostName string) (*MachineNode, error)
}
customMachineNodeModel struct {
*defaultMachineNodeModel
}
)
// NewMachineNodeModel returns a model for the database table.
func NewMachineNodeModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) MachineNodeModel {
return &customMachineNodeModel{
defaultMachineNodeModel: newMachineNodeModel(conn, c, opts...),
}
}
func (m *defaultMachineNodeModel) FindOneByHostName(ctx context.Context, hostName string) (*MachineNode, error) {
machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, hostName)
var resp MachineNode
err := m.QueryRowCtx(ctx, &resp, machineNodeIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `host_name` = ? limit 1", machineNodeRows, m.table)
return conn.QueryRowCtx(ctx, v, query, hostName)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}

View File

@ -0,0 +1,117 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
machineNodeFieldNames = builder.RawFieldNames(&MachineNode{})
machineNodeRows = strings.Join(machineNodeFieldNames, ",")
machineNodeRowsExpectAutoSet = strings.Join(stringx.Remove(machineNodeFieldNames, "`id`"), ",")
machineNodeRowsWithPlaceHolder = strings.Join(stringx.Remove(machineNodeFieldNames, "`id`"), "=?,") + "=?"
cacheMachineNodeIdPrefix = "cache:machineNode:id:"
)
type (
machineNodeModel interface {
Insert(ctx context.Context, data *MachineNode) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*MachineNode, error)
Update(ctx context.Context, data *MachineNode) error
Delete(ctx context.Context, id int64) error
}
defaultMachineNodeModel struct {
sqlc.CachedConn
table string
}
MachineNode struct {
Id int64 `db:"id"` // 流水號
CreateTime int64 `db:"create_time"` // 創建時間
UpdateTime int64 `db:"update_time"` // 更新時間
HostName string `db:"host_name"` // host name
}
)
func newMachineNodeModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultMachineNodeModel {
return &defaultMachineNodeModel{
CachedConn: sqlc.NewConn(conn, c, opts...),
table: "`machine_node`",
}
}
func (m *defaultMachineNodeModel) withSession(session sqlx.Session) *defaultMachineNodeModel {
return &defaultMachineNodeModel{
CachedConn: m.CachedConn.WithSession(session),
table: "`machine_node`",
}
}
func (m *defaultMachineNodeModel) Delete(ctx context.Context, id int64) error {
machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, id)
_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, machineNodeIdKey)
return err
}
func (m *defaultMachineNodeModel) FindOne(ctx context.Context, id int64) (*MachineNode, error) {
machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, id)
var resp MachineNode
err := m.QueryRowCtx(ctx, &resp, machineNodeIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", machineNodeRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultMachineNodeModel) Insert(ctx context.Context, data *MachineNode) (sql.Result, error) {
machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, data.Id)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?)", m.table, machineNodeRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.CreateTime, data.UpdateTime, data.HostName)
}, machineNodeIdKey)
return ret, err
}
func (m *defaultMachineNodeModel) Update(ctx context.Context, data *MachineNode) error {
machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, data.Id)
_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, machineNodeRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, data.CreateTime, data.UpdateTime, data.HostName, data.Id)
}, machineNodeIdKey)
return err
}
func (m *defaultMachineNodeModel) formatPrimary(primary any) string {
return fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, primary)
}
func (m *defaultMachineNodeModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", machineNodeRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultMachineNodeModel) tableName() string {
return m.table
}

View File

@ -0,0 +1,27 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ UserTableModel = (*customUserTableModel)(nil)
type (
// UserTableModel is an interface to be customized, add more methods here,
// and implement the added methods in customUserTableModel.
UserTableModel interface {
userTableModel
}
customUserTableModel struct {
*defaultUserTableModel
}
)
// NewUserTableModel returns a model for the database table.
func NewUserTableModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) UserTableModel {
return &customUserTableModel{
defaultUserTableModel: newUserTableModel(conn, c, opts...),
}
}

View File

@ -0,0 +1,161 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
userTableFieldNames = builder.RawFieldNames(&UserTable{})
userTableRows = strings.Join(userTableFieldNames, ",")
userTableRowsExpectAutoSet = strings.Join(stringx.Remove(userTableFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",")
userTableRowsWithPlaceHolder = strings.Join(stringx.Remove(userTableFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?"
cacheUserTableIdPrefix = "cache:userTable:id:"
cacheUserTableUidPrefix = "cache:userTable:uid:"
)
type (
userTableModel interface {
Insert(ctx context.Context, data *UserTable) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*UserTable, error)
FindOneByUid(ctx context.Context, uid string) (*UserTable, error)
Update(ctx context.Context, data *UserTable) error
Delete(ctx context.Context, id int64) error
}
defaultUserTableModel struct {
sqlc.CachedConn
table string
}
UserTable struct {
Id int64 `db:"id"`
VerifyType int64 `db:"verify_type"` // 驗證類型 0. 異常 1.信箱 2.手機 3. GA 4.不驗證
AlarmType int64 `db:"alarm_type"` // 告警狀態 0. 異常 1. 正常(未告警) 2.系統告警中
Status int64 `db:"status"` // 會員狀態 0. 異常 1. 尚未驗證 2. 啟用 3. 停權中 4. 信箱以驗證 5. 手機以驗證 6. GA 以驗證
Uid string `db:"uid"`
RoleId string `db:"role_id"`
Language string `db:"language"`
Currency string `db:"currency"`
NickName string `db:"nick_name"`
Gender int64 `db:"gender"` // 0. 不願透露, 1 男 2 女
Birthday int64 `db:"birthday"`
CreateTime int64 `db:"create_time"`
UpdateTime int64 `db:"update_time"`
}
)
func newUserTableModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultUserTableModel {
return &defaultUserTableModel{
CachedConn: sqlc.NewConn(conn, c, opts...),
table: "`user_table`",
}
}
func (m *defaultUserTableModel) withSession(session sqlx.Session) *defaultUserTableModel {
return &defaultUserTableModel{
CachedConn: m.CachedConn.WithSession(session),
table: "`user_table`",
}
}
func (m *defaultUserTableModel) Delete(ctx context.Context, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
userTableIdKey := fmt.Sprintf("%s%v", cacheUserTableIdPrefix, id)
userTableUidKey := fmt.Sprintf("%s%v", cacheUserTableUidPrefix, data.Uid)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, userTableIdKey, userTableUidKey)
return err
}
func (m *defaultUserTableModel) FindOne(ctx context.Context, id int64) (*UserTable, error) {
userTableIdKey := fmt.Sprintf("%s%v", cacheUserTableIdPrefix, id)
var resp UserTable
err := m.QueryRowCtx(ctx, &resp, userTableIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userTableRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserTableModel) FindOneByUid(ctx context.Context, uid string) (*UserTable, error) {
userTableUidKey := fmt.Sprintf("%s%v", cacheUserTableUidPrefix, uid)
var resp UserTable
err := m.QueryRowIndexCtx(ctx, &resp, userTableUidKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
query := fmt.Sprintf("select %s from %s where `uid` = ? limit 1", userTableRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, uid); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserTableModel) Insert(ctx context.Context, data *UserTable) (sql.Result, error) {
userTableIdKey := fmt.Sprintf("%s%v", cacheUserTableIdPrefix, data.Id)
userTableUidKey := fmt.Sprintf("%s%v", cacheUserTableUidPrefix, data.Uid)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, userTableRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.VerifyType, data.AlarmType, data.Status, data.Uid, data.RoleId, data.Language, data.Currency, data.NickName, data.Gender, data.Birthday)
}, userTableIdKey, userTableUidKey)
return ret, err
}
func (m *defaultUserTableModel) Update(ctx context.Context, newData *UserTable) error {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
userTableIdKey := fmt.Sprintf("%s%v", cacheUserTableIdPrefix, data.Id)
userTableUidKey := fmt.Sprintf("%s%v", cacheUserTableUidPrefix, data.Uid)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userTableRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.VerifyType, newData.AlarmType, newData.Status, newData.Uid, newData.RoleId, newData.Language, newData.Currency, newData.NickName, newData.Gender, newData.Birthday, newData.Id)
}, userTableIdKey, userTableUidKey)
return err
}
func (m *defaultUserTableModel) formatPrimary(primary any) string {
return fmt.Sprintf("%s%v", cacheUserTableIdPrefix, primary)
}
func (m *defaultUserTableModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userTableRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultUserTableModel) tableName() string {
return m.table
}

5
internal/model/vars.go Normal file
View File

@ -0,0 +1,5 @@
package model
import "github.com/zeromicro/go-zero/core/stores/sqlx"
var ErrNotFound = sqlx.ErrNotFound

View File

@ -0,0 +1,95 @@
// Code generated by goctl. DO NOT EDIT.
// Source: member.proto
package server
import (
"context"
"member/gen_result/pb/member"
"member/internal/logic"
"member/internal/svc"
)
type AccountServer struct {
svcCtx *svc.ServiceContext
member.UnimplementedAccountServer
}
func NewAccountServer(svcCtx *svc.ServiceContext) *AccountServer {
return &AccountServer{
svcCtx: svcCtx,
}
}
// CreateUserAccount 建立帳號與密碼 -> 可登入,但可不可以做其他事情看業務流程,也可以只註冊就好
func (s *AccountServer) CreateUserAccount(ctx context.Context, in *member.CreateLoginUserReq) (*member.Response, error) {
l := logic.NewCreateUserAccountLogic(ctx, s.svcCtx)
return l.CreateUserAccount(in)
}
// GetUserAccountInfo 取得帳號密碼資料
func (s *AccountServer) GetUserAccountInfo(ctx context.Context, in *member.GetUIDByAccountReq) (*member.GetAccountInfoResp, error) {
l := logic.NewGetUserAccountInfoLogic(ctx, s.svcCtx)
return l.GetUserAccountInfo(in)
}
// UpdateUserToken 更新密碼
func (s *AccountServer) UpdateUserToken(ctx context.Context, in *member.UpdateTokenReq) (*member.Response, error) {
l := logic.NewUpdateUserTokenLogic(ctx, s.svcCtx)
return l.UpdateUserToken(in)
}
// GetUidByAccount 用帳號換取 UID
func (s *AccountServer) GetUidByAccount(ctx context.Context, in *member.GetUIDByAccountReq) (*member.GetUidByAccountResp, error) {
l := logic.NewGetUidByAccountLogic(ctx, s.svcCtx)
return l.GetUidByAccount(in)
}
// BindAccount 綁定帳號 -> account bind to UID
func (s *AccountServer) BindAccount(ctx context.Context, in *member.BindingUserReq) (*member.Response, error) {
l := logic.NewBindAccountLogic(ctx, s.svcCtx)
return l.BindAccount(in)
}
// BindUserInfo 初次,綁定 User Info
func (s *AccountServer) BindUserInfo(ctx context.Context, in *member.CreateUserInfoReq) (*member.Response, error) {
l := logic.NewBindUserInfoLogic(ctx, s.svcCtx)
return l.BindUserInfo(in)
}
// UpdateUserInfo 更新 User Info
func (s *AccountServer) UpdateUserInfo(ctx context.Context, in *member.UpdateUserInfoReq) (*member.Response, error) {
l := logic.NewUpdateUserInfoLogic(ctx, s.svcCtx)
return l.UpdateUserInfo(in)
}
// UpdateStatus 修改狀態
func (s *AccountServer) UpdateStatus(ctx context.Context, in *member.UpdateStatusReq) (*member.Response, error) {
l := logic.NewUpdateStatusLogic(ctx, s.svcCtx)
return l.UpdateStatus(in)
}
// UpdateStatus 取得會員資訊
func (s *AccountServer) GetUserInfo(ctx context.Context, in *member.GetUserInfoReq) (*member.GetUserInfoResp, error) {
l := logic.NewGetUserInfoLogic(ctx, s.svcCtx)
return l.GetUserInfo(in)
}
// ListMember 取得會員列表
func (s *AccountServer) ListMember(ctx context.Context, in *member.ListUserInfoReq) (*member.ListUserInfoResp, error) {
l := logic.NewListMemberLogic(ctx, s.svcCtx)
return l.ListMember(in)
}
// GenerateRefreshCode 這個帳號驗證碼(十分鐘),通用的
func (s *AccountServer) GenerateRefreshCode(ctx context.Context, in *member.GenerateRefreshCodeReq) (*member.GenerateRefreshCodeResp, error) {
l := logic.NewGenerateRefreshCodeLogic(ctx, s.svcCtx)
return l.GenerateRefreshCode(in)
}
// VerifyRefreshCode 驗證忘記密碼 token
func (s *AccountServer) VerifyRefreshCode(ctx context.Context, in *member.VerifyRefreshCodeReq) (*member.Response, error) {
l := logic.NewVerifyRefreshCodeLogic(ctx, s.svcCtx)
return l.VerifyRefreshCode(in)
}

View File

@ -0,0 +1,66 @@
package svc
import (
"context"
"github.com/bwmarrin/snowflake"
sf "member/internal/lib/snackflow"
"member/internal/model"
"os"
"time"
)
type machineNode struct {
MachineNodeID int64 `json:"machine_node_id"`
}
type MachineNodeCreateParams struct {
HostName string
}
func NewMachineNode(node model.MachineNodeModel) int64 {
ctx := context.Background()
nodeName := os.Getenv("POD_NAME")
if os.Getenv("POD_NAME") == "" {
nodeName = "default_node"
}
machine, err := node.FindOneByHostName(ctx, nodeName)
if err != nil {
result, err := node.Insert(ctx, &model.MachineNode{
CreateTime: time.Now().Unix(),
HostName: nodeName,
})
if err != nil {
return 1
}
id, err := result.LastInsertId()
if err != nil {
return 1
}
return id
}
return machine.Id
}
func GetMachineNodeID(machineNodeID int64) int64 {
// Snowflake 公式,工作機器 ID 佔用 10bit最多容納 1024節點
// 故用 % 1024 取餘數做 ring
const nodeMax = 1024
return machineNodeID % nodeMax
}
func newSnackFlowNode(node model.MachineNodeModel) (*snowflake.Node, error) {
nodeID := NewMachineNode(node)
ringNodeID := GetMachineNodeID(nodeID)
s := sf.New(sf.WithMachineNodeID(ringNodeID))
n, err := s.NewNode()
if err != nil {
return nil, err
}
return n, nil
}

View File

@ -0,0 +1,43 @@
package svc
import (
"github.com/bwmarrin/snowflake"
"member/internal/config"
"member/internal/domain"
"member/internal/lib/required"
"member/internal/model"
"github.com/go-playground/validator/v10"
"github.com/zeromicro/go-zero/core/stores/sqlx"
ers "member/internal/lib/error"
)
type ServiceContext struct {
Config config.Config
Validate *validator.Validate
AccountModel model.AccountModel
UserModel model.UserTableModel
AccountToUidModel model.AccountToUidModel
SnackFlowGen *snowflake.Node
}
func NewServiceContext(c config.Config) *ServiceContext {
sqlConn := sqlx.NewMysql(c.DB.DsnString)
// 設置
ers.Scope = domain.Scope
n, err := newSnackFlowNode(model.NewMachineNodeModel(sqlConn, c.Cache))
if err != nil {
panic(err)
}
return &ServiceContext{
Config: c,
Validate: required.MustValidator(required.WithAccount("account")),
UserModel: model.NewUserTableModel(sqlConn, c.Cache),
AccountToUidModel: model.NewAccountToUidModel(sqlConn, c.Cache),
AccountModel: model.NewAccountModel(sqlConn, c.Cache),
SnackFlowGen: n,
}
}

View File

@ -0,0 +1,20 @@
package utils
import (
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string, cost int) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func GetHashingCost(hashedPassword []byte) int {
cost, _ := bcrypt.Cost(hashedPassword)
return cost
}

105
makefile Normal file
View File

@ -0,0 +1,105 @@
.PHONY: help test-race lint sec-scan gci-format db-mysql-init docker-image-build db-mysql-down generate
help: ## show how tot use this tools
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
PROJECT_NAME?=member
########
# test #
########
test-race: ## launch all tests with race detection
go test ./... -cover -race
########
# lint #
########
lint: ## lints the entire codebase
@golangci-lint run ./... --config=./.golangci.yaml
#######
# sec #
#######
sec-scan: trivy-scan vuln-scan ## scan for security and vulnerability issues
trivy-scan: ## scan for sec issues with trivy (trivy binary needed)
trivy fs --exit-code 1 --no-progress --severity CRITICAL ./
vuln-scan: ## scan for vulnerability issues with govulncheck (govulncheck binary needed)
govulncheck ./...
######
# db #
######
MYSQL_SQL_PATH="./database/migrations/mysql"
db-mysql-init:
@( \
printf "Enter migrate name: "; read -r MIGRATE_NAME && \
migrate create -ext sql -dir ${MYSQL_SQL_PATH} $${MIGRATE_NAME} \
)
db-mysql-up:
@( \
printf "Enter pass for db: \n"; read -rs DB_PASSWORD && \
printf "Enter port(3306...): \n"; read -r DB_PORT &&\
migrate --database "mysql://root:$${DB_PASSWORD}@tcp(localhost:$${DB_PORT})/$(PROJECT_NAME)?charset=utf8&parseTime=True&loc=Local" --path ${MYSQL_SQL_PATH} up \
)
db-mysql-down:
@( \
printf "Enter pass for db: \n"; read -s DB_PASSWORD && \
printf "Enter port(3306...): \n"; read -r DB_PORT &&\
migrate --database "mysql://root:$${DB_PASSWORD}@tcp(localhost:$${DB_PORT})/$(PROJECT_NAME)?charset=utf8&parseTime=True&loc=Local" --path ${MYSQL_SQL_PATH} down \
)
SQL_FILE_TIMESTAMP=$(shell date '+%Y%m%d%H%M%S')
gen-migrate-sql:
@( \
printf "Enter file name: "; read -r FILE_NAME; \
touch database/migrations/mysql/$(SQL_FILE_TIMESTAMP)_$$FILE_NAME.up.sql; \
touch database/migrations/mysql/$(SQL_FILE_TIMESTAMP)_$$FILE_NAME.down.sql; \
)
###########
# GCI #
###########
gci-format:
gci write --skip-generated -s standard -s default -s "prefix(yt.com/backend)" -s "prefix($(PROJECT_NAME))" ./
#########
# build #
#########
GitCommit=$(shell git rev-parse HEAD)
Date=$(shell date -Iseconds)
build:
@( \
printf "Enter file name: "; read -r VERSION; \
go build -ldflags "-s -w -X 'main.Version=$$VERSION' -X 'main.Built=$(Date)' -X 'main.GitCommit=$(GitCommit)'" -o ./bin/$(PROJECT_NAME) ./cmd/$(PROJECT_NAME) \
)
docker-image-build:
docker build \
-f ./build/Dockerfile \
-t $(PROJECT_NAME) \
--platform linux/amd64 \
--build-arg BUILT=$(Date) \
--build-arg GIT_COMMIT=$(GitCommit) \
--ssh default=$$HOME/.ssh/id_rsa \
./
generate:
@( \
printf "Generate protobuf .... "; \
# goctl rpc protoc ./generate/protobuf/member.proto --style=go_zero --go_out=./gen_result/pb --go-grpc_out=./gen_result/pb --zrpc_out=. \
# printf "Generate docker .... "; \
# goctl docker --go member.go --exe member --version 1.22 --tz Asia/Taipei --remote code.30cm.net --base gcr.io/distroless/static-debian12
# goctl model mysql ddl -c yes -s ./generate/database/mysql/20230529020011_account_uid_table.up.sql --style go_zero -d ./internal/model
)

43
member.go Normal file
View File

@ -0,0 +1,43 @@
package main
import (
"flag"
"fmt"
"member/internal/lib/middleware"
"member/gen_result/pb/member"
"member/internal/config"
"member/internal/server"
"member/internal/svc"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/service"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
var configFile = flag.String("f", "etc/member.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
member.RegisterAccountServer(grpcServer, server.NewAccountServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})
defer s.Stop()
// 加入勿中間件
s.AddUnaryInterceptors(middleware.TimeoutMiddleware)
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}

14
readme.md Normal file
View File

@ -0,0 +1,14 @@
### Install
```bash
goctl
go get -d github.com/envoyproxy/protoc-gen-validate
```
## GRPC Validate
https://github.com/anoyah/go-zero-validate
https://github.com/bufbuild/protoc-gen-validate