16 changed files with 404 additions and 16 deletions
@ -0,0 +1,2 @@ |
|||||||
|
|
||||||
|
aesTokenKey = "VzKw8Vx+K8k1nO9fmPjhv8o+8l4vqpF+fjsbvXf0j4o=" |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
package args |
||||||
|
|
||||||
|
import "errors" |
||||||
|
|
||||||
|
type LoginReq struct { |
||||||
|
Account string `json:"account"` |
||||||
|
Password string `json:"password"` |
||||||
|
} |
||||||
|
|
||||||
|
func (arg LoginReq) Validate() (err error) { |
||||||
|
if len(arg.Account) == 0 { |
||||||
|
err = errors.New("account is empty") |
||||||
|
return |
||||||
|
} |
||||||
|
if len(arg.Account) > 36 { |
||||||
|
err = errors.New("account length must be at most 36") |
||||||
|
return |
||||||
|
} |
||||||
|
if len(arg.Password) < 6 || len(arg.Password) > 128 { |
||||||
|
err = errors.New("password length must be at least 6 and at most 128") |
||||||
|
return |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
package repository |
||||||
|
|
||||||
|
import ( |
||||||
|
"sig-pub/pkg/data" |
||||||
|
"sig-pub/pkg/data/entity" |
||||||
|
"sig-pub/pkg/storage/persist" |
||||||
|
"sig-pub/pkg/utils/rands" |
||||||
|
) |
||||||
|
|
||||||
|
type UserRepository struct { |
||||||
|
db *persist.DB |
||||||
|
} |
||||||
|
|
||||||
|
func NewUserRepository(db *persist.DB) *UserRepository { |
||||||
|
return &UserRepository{ |
||||||
|
db: db, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *UserRepository) ListAllInstanceId() (insts []string, err error) { |
||||||
|
err = s.db.Select(&insts, `select inst_id from t_trade_instance where status != ?`, data.StatusDeleted) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (dao *UserRepository) FindByAccount(account string) (user *entity.User, err error) { |
||||||
|
user = &entity.User{} |
||||||
|
err = dao.db.Select(user, `select * from t_user where account=?`, account) |
||||||
|
return user, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (dao *UserRepository) NextUid() (nextUid int64, err error) { |
||||||
|
maxUid := int64(0) |
||||||
|
err = dao.db.Select(&maxUid, "uid=(select userid from t_user)") |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
if maxUid == 0 { |
||||||
|
maxUid = 10000 |
||||||
|
} |
||||||
|
|
||||||
|
nextUid = maxUid + rands.RandN(10) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (dao *UserRepository) Create(user *entity.User) (err error) { |
||||||
|
err = dao.db.Insert(user) |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,95 @@ |
|||||||
|
package service |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/base64" |
||||||
|
"net/http" |
||||||
|
"sig-pub/internal/admin/args" |
||||||
|
repository "sig-pub/internal/admin/repoitory" |
||||||
|
"sig-pub/pkg/config" |
||||||
|
"sig-pub/pkg/data/entity" |
||||||
|
"sig-pub/pkg/resp" |
||||||
|
"sig-pub/pkg/session" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/gin-gonic/gin" |
||||||
|
) |
||||||
|
|
||||||
|
type AuthService struct { |
||||||
|
repo *repository.UserRepository |
||||||
|
aesKeyBytes []byte |
||||||
|
} |
||||||
|
|
||||||
|
func NewAuthService(repo *repository.UserRepository) *AuthService { |
||||||
|
return &AuthService{ |
||||||
|
repo: repo, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (svc *AuthService) Route(group *gin.RouterGroup) { |
||||||
|
aesKey, err := config.GetString("aesTokenKey") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
svc.aesKeyBytes, err = base64.StdEncoding.DecodeString(aesKey) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *AuthService) Login(ctx *gin.Context) { |
||||||
|
var req args.LoginReq |
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil { |
||||||
|
ctx.JSON(http.StatusBadRequest, resp.Fail(err.Error())) |
||||||
|
return |
||||||
|
} |
||||||
|
if err := req.Validate(); err != nil { |
||||||
|
ctx.JSON(http.StatusBadRequest, resp.Fail(err.Error())) |
||||||
|
return |
||||||
|
} |
||||||
|
user, err := s.repo.FindByAccount(req.Account) |
||||||
|
if err != nil { |
||||||
|
ctx.JSON(http.StatusInternalServerError, resp.Fail(err.Error())) |
||||||
|
return |
||||||
|
} |
||||||
|
if len(req.Password) < 6 { |
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "password length must be at least 6"}) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if user == nil { |
||||||
|
// 不存在注册
|
||||||
|
now := time.Now().UnixMilli() |
||||||
|
uid, err := s.repo.NextUid() |
||||||
|
if err != nil { |
||||||
|
ctx.JSON(http.StatusInternalServerError, resp.Fail(err.Error())) |
||||||
|
return |
||||||
|
} |
||||||
|
user = &entity.User{ |
||||||
|
UserId: uid, |
||||||
|
Account: req.Account, |
||||||
|
Username: req.Account, |
||||||
|
Password: req.Password, |
||||||
|
CreateAt: now, |
||||||
|
UpdateAt: now, |
||||||
|
} |
||||||
|
err = s.repo.Create(user) |
||||||
|
if err != nil { |
||||||
|
ctx.JSON(http.StatusInternalServerError, resp.Fail(err.Error())) |
||||||
|
return |
||||||
|
} |
||||||
|
// return nil, errors.New("not found account " + req.Account)
|
||||||
|
} |
||||||
|
// verify password login
|
||||||
|
if req.Password != user.Password { |
||||||
|
ctx.JSON(http.StatusBadRequest, resp.Fail("account or password error")) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// generate token
|
||||||
|
rpcSubject := session.NewRpcSubject(user.UserId, user.Account, nil) |
||||||
|
_ = rpcSubject |
||||||
|
// session.RpcSubjectGenToken()
|
||||||
|
|
||||||
|
// res := &auth.ResLogin{Token: token, Subject: subject}
|
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
package config |
||||||
|
|
||||||
|
import "github.com/spf13/viper" |
||||||
|
|
||||||
|
var ( |
||||||
|
defaultConfigPath = "config/config.toml" |
||||||
|
namespaceConfigPath = map[string]string{} |
||||||
|
) |
||||||
|
|
||||||
|
func SetDefaultConfigPath(confPath string) { |
||||||
|
defaultConfigPath = confPath |
||||||
|
} |
||||||
|
|
||||||
|
func SetNamespaceConfigPath(namespace string, confPath string) { |
||||||
|
namespaceConfigPath[namespace] = confPath |
||||||
|
} |
||||||
|
|
||||||
|
func getViper(namespace ...string) (v *viper.Viper, err error) { |
||||||
|
confPath := defaultConfigPath |
||||||
|
if len(namespace) > 0 { |
||||||
|
confPath = namespaceConfigPath[namespace[0]] |
||||||
|
} |
||||||
|
v, err = loadViper(confPath) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func GetString(key string, namespace ...string) (value string, err error) { |
||||||
|
v, err := getViper(namespace...) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
value = v.GetString(key) |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
package entity |
||||||
|
|
||||||
|
// User 用户
|
||||||
|
type User struct { |
||||||
|
UserId int64 `gorm:"column:userid;primaryKey;" json:"userId"` // 用户id
|
||||||
|
Account string `gorm:"column:account" json:"account"` // 登录账号
|
||||||
|
Username string `gorm:"column:username" json:"username"` // 用户名
|
||||||
|
Password string `gorm:"column:password" json:"password"` // 密码
|
||||||
|
Avatar string `gorm:"column:avatar" json:"avatar"` // 头像
|
||||||
|
CreateAt int64 `gorm:"column:create_at" json:"createAt"` // 创建时间
|
||||||
|
UpdateAt int64 `gorm:"column:update_at" json:"updateAt"` // 更新时间
|
||||||
|
} |
||||||
|
|
||||||
|
func (User) TableName() string { |
||||||
|
return "t_user" |
||||||
|
} |
||||||
@ -0,0 +1,92 @@ |
|||||||
|
package indicator |
||||||
|
|
||||||
|
import ( |
||||||
|
"sig-pub/pkg/types" |
||||||
|
) |
||||||
|
|
||||||
|
// RVI Relative Vigor Index
|
||||||
|
// RVI = SMA(Num, N) / SMA(Denom, N)
|
||||||
|
// Num = (Close-Open) + 2*(Close_1-Open_1) + 2*(Close_2-Open_2) + (Close_3-Open_3)
|
||||||
|
// Denom = (High-Low) + 2*(High_1-Low_1) + 2*(High_2-Low_2) + (High_3-Low_3)
|
||||||
|
// Signal = (RVI + 2*RVI_1 + 2*RVI_2 + RVI_3) / 6
|
||||||
|
type RVI struct { |
||||||
|
} |
||||||
|
|
||||||
|
func (c *RVI) Meta() IndicatorMeta { |
||||||
|
return IndicatorMeta{ |
||||||
|
Name: "RVI", |
||||||
|
Input: []types.InputArg{ |
||||||
|
{Name: "window", Type: types.InputTypeUInt, Desc: "窗口大小", Default: 10}, |
||||||
|
}, |
||||||
|
State: []string{"sig"}, |
||||||
|
Plots: []Plot{ |
||||||
|
{Name: "RVI", State: "vector", Type: PlotLine, Props: PlotProps{"color": ColorGreen}}, |
||||||
|
{Name: "Signal", State: "sig", Type: PlotLine, Props: PlotProps{"color": ColorRed}}, |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *RVI) CandlePeriods(ctx IIndicatorContext) int16 { |
||||||
|
return ctx.Input().Int16("window") + 3 |
||||||
|
} |
||||||
|
|
||||||
|
func (c *RVI) Calculate(ctx IIndicatorContext) (vector float64) { |
||||||
|
window := ctx.Input().Int16("window") |
||||||
|
|
||||||
|
count := window + 3 |
||||||
|
klines := ctx.Series(0, count) |
||||||
|
|
||||||
|
if len(klines) < int(count) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
var numSum, denomSum float64 |
||||||
|
|
||||||
|
for i := int16(0); i < window; i++ { |
||||||
|
k0 := klines[i] |
||||||
|
k1 := klines[i+1] |
||||||
|
k2 := klines[i+2] |
||||||
|
k3 := klines[i+3] |
||||||
|
|
||||||
|
// CO = Close - Open
|
||||||
|
co0 := k0.CloseF64() - k0.OpenF64() |
||||||
|
co1 := k1.CloseF64() - k1.OpenF64() |
||||||
|
co2 := k2.CloseF64() - k2.OpenF64() |
||||||
|
co3 := k3.CloseF64() - k3.OpenF64() |
||||||
|
|
||||||
|
// HL = High - Low
|
||||||
|
hl0 := k0.HighF64() - k0.LowF64() |
||||||
|
hl1 := k1.HighF64() - k1.LowF64() |
||||||
|
hl2 := k2.HighF64() - k2.LowF64() |
||||||
|
hl3 := k3.HighF64() - k3.LowF64() |
||||||
|
|
||||||
|
val1 := (co0 + 2*co1 + 2*co2 + co3) / 6.0 |
||||||
|
val2 := (hl0 + 2*hl1 + 2*hl2 + hl3) / 6.0 |
||||||
|
|
||||||
|
numSum += val1 |
||||||
|
denomSum += val2 |
||||||
|
} |
||||||
|
|
||||||
|
var rvi float64 |
||||||
|
if denomSum != 0 { |
||||||
|
rvi = numSum / denomSum |
||||||
|
} |
||||||
|
|
||||||
|
ctx.State().Set("rvi", rvi) |
||||||
|
|
||||||
|
// Calculate Signal
|
||||||
|
// Signal = (RVI + 2*RVI_1 + 2*RVI_2 + RVI_3) / 6
|
||||||
|
rvi1, ok1 := ctx.State().Get("rvi", 1) |
||||||
|
rvi2, ok2 := ctx.State().Get("rvi", 2) |
||||||
|
rvi3, ok3 := ctx.State().Get("rvi", 3) |
||||||
|
|
||||||
|
var sig float64 |
||||||
|
if ok1 && ok2 && ok3 { |
||||||
|
sig = (rvi + 2*rvi1 + 2*rvi2 + rvi3) / 6.0 |
||||||
|
} else { |
||||||
|
sig = rvi |
||||||
|
} |
||||||
|
ctx.State().Set("sig", sig) |
||||||
|
|
||||||
|
return rvi |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
package session |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/base64" |
||||||
|
"sig-pub/api/pb" |
||||||
|
"sig-pub/pkg/utils/security" |
||||||
|
"time" |
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto" |
||||||
|
) |
||||||
|
|
||||||
|
func NewRpcSubject(userId int64, account string, extra map[string]string) (subject *pb.RpcSubject) { |
||||||
|
subject = &pb.RpcSubject{Uid: userId, Account: account, Time: time.Now().UnixMilli(), Extra: extra} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func RpcSubjectGenToken(aesKey []byte, subject *pb.RpcSubject) (token string, err error) { |
||||||
|
bytes, err := proto.Marshal(subject) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
t, err := security.EncryptAesCBC(bytes, aesKey) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
token = base64.URLEncoding.EncodeToString(t) |
||||||
|
return |
||||||
|
} |
||||||
Loading…
Reference in new issue