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