home
This commit is contained in:
parent
d7aea82ced
commit
bc3f5e0b0c
@ -2,6 +2,7 @@
|
||||
|
||||
统一验证服务
|
||||
|
||||
|
||||
## 用户验证思路
|
||||
|
||||

|
||||
|
||||
@ -3,22 +3,25 @@ package api
|
||||
import (
|
||||
"OneAuth/api/app"
|
||||
"OneAuth/api/role"
|
||||
"OneAuth/api/token"
|
||||
"OneAuth/api/user"
|
||||
"OneAuth/api/wx"
|
||||
"OneAuth/libs/base"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/core"
|
||||
)
|
||||
|
||||
func Router(r OneBD.Router) {
|
||||
r.SetNotFoundFunc(func(m core.Meta) {
|
||||
m.Write([]byte("{\"status\": 0}"))
|
||||
base.JSONResponse(m, nil, nil)
|
||||
})
|
||||
r.SetInternalErrorFunc(func(m core.Meta) {
|
||||
m.Write([]byte("{\"status\": 0}"))
|
||||
base.JSONResponse(m, nil, nil)
|
||||
})
|
||||
user.Router(r.SubRouter("/user"))
|
||||
wx.Router(r.SubRouter("wx"))
|
||||
app.Router(r.SubRouter("app"))
|
||||
token.Router(r.SubRouter("token"))
|
||||
role.Router(r)
|
||||
|
||||
//message.Router(r.SubRouter("/message"))
|
||||
|
||||
@ -8,15 +8,17 @@ import (
|
||||
"OneAuth/models"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
"github.com/veypi/utils"
|
||||
)
|
||||
|
||||
func Router(r OneBD.Router) {
|
||||
r.Set("/", appHandlerP, rfc.MethodPost, rfc.MethodGet)
|
||||
r.Set("/:id", appHandlerP, rfc.MethodGet)
|
||||
}
|
||||
|
||||
var appHandlerP = OneBD.NewHandlerPool(func() OneBD.Handler {
|
||||
h := &appHandler{}
|
||||
h.Ignore(rfc.MethodGet)
|
||||
h.Ignore(rfc.MethodGet, rfc.MethodPost)
|
||||
return h
|
||||
})
|
||||
|
||||
@ -31,7 +33,7 @@ func (h *appHandler) Get() (interface{}, error) {
|
||||
isSelf := h.Meta().Query("is_self")
|
||||
if isSelf != "" {
|
||||
// 无权限可以获取本系统基本信息
|
||||
h.query.ID = cfg.CFG.APPID
|
||||
h.query.UUID = cfg.CFG.APPUUID
|
||||
err := cfg.DB().Where(h.query).First(h.query).Error
|
||||
return h.query, err
|
||||
}
|
||||
@ -64,3 +66,35 @@ func (h *appHandler) Get() (interface{}, error) {
|
||||
err = cfg.DB().Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (h *appHandler) Post() (interface{}, error) {
|
||||
data := &struct {
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
}{}
|
||||
err := h.Meta().ReadJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if data.Name == "" {
|
||||
return nil, oerr.ApiArgsMissing.AttachStr("name")
|
||||
}
|
||||
_ = h.ParsePayload(h.Meta())
|
||||
a := &models.App{
|
||||
UUID: data.UUID,
|
||||
Name: data.Name,
|
||||
Key: utils.RandSeq(32),
|
||||
Creator: h.Payload.ID,
|
||||
}
|
||||
a.Key = utils.RandSeq(32)
|
||||
if data.UUID != "" {
|
||||
err = cfg.DB().Where("uuid = ?", data.UUID).FirstOrCreate(a).Error
|
||||
} else {
|
||||
data.UUID = utils.RandSeq(16)
|
||||
err = cfg.DB().Create(a).Error
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
69
api/token/token.go
Normal file
69
api/token/token.go
Normal file
@ -0,0 +1,69 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/app"
|
||||
"OneAuth/libs/base"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/libs/token"
|
||||
"OneAuth/models"
|
||||
"errors"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Router(r OneBD.Router) {
|
||||
p := OneBD.NewHandlerPool(func() OneBD.Handler {
|
||||
return &tokenHandler{}
|
||||
})
|
||||
r.Set("/:uuid", p, rfc.MethodGet)
|
||||
}
|
||||
|
||||
type tokenHandler struct {
|
||||
base.ApiHandler
|
||||
}
|
||||
|
||||
func (h *tokenHandler) Get() (interface{}, error) {
|
||||
uuid := h.Meta().Params("uuid")
|
||||
if uuid == "" {
|
||||
return nil, oerr.ApiArgsMissing.AttachStr("uuid")
|
||||
}
|
||||
a := &models.App{}
|
||||
a.UUID = uuid
|
||||
err := cfg.DB().Where("uuid = ?", uuid).First(a).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
au := &models.AppUser{
|
||||
UserID: h.Payload.ID,
|
||||
AppID: a.ID,
|
||||
}
|
||||
err = cfg.DB().Where(au).First(au).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if a.EnableRegister {
|
||||
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||
return app.AddUser(cfg.DB(), au.AppID, au.UserID, a.InitRoleID, models.AUOK)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
au.Status = models.AUOK
|
||||
} else {
|
||||
return nil, oerr.AppNotJoin.AttachStr(a.Name)
|
||||
}
|
||||
}
|
||||
return nil, oerr.DBErr.Attach(err)
|
||||
}
|
||||
if au.Status != models.AUOK {
|
||||
return nil, oerr.NoAuth.AttachStr(string(au.Status))
|
||||
}
|
||||
u := &models.User{}
|
||||
err = cfg.DB().Preload("Auths").Preload("Roles.Auths").Where("id = ?", h.Payload.ID).First(u).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := token.GetToken(u, a.ID, a.Key)
|
||||
return t, err
|
||||
}
|
||||
@ -3,13 +3,11 @@ package user
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/app"
|
||||
"OneAuth/libs/auth"
|
||||
"OneAuth/libs/base"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/libs/token"
|
||||
"OneAuth/models"
|
||||
"errors"
|
||||
|
||||
//"OneAuth/ws"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/veypi/OneBD"
|
||||
@ -43,10 +41,13 @@ type handler struct {
|
||||
|
||||
// Get get user data
|
||||
func (h *handler) Get() (interface{}, error) {
|
||||
if !h.Payload.GetAuth(auth.User, "").CanRead() {
|
||||
return nil, oerr.NoAuth.AttachStr("to read user list")
|
||||
}
|
||||
username := h.Meta().Query("username")
|
||||
if username != "" {
|
||||
users := make([]*models.User, 0, 10)
|
||||
err := cfg.DB().Preload("Scores").Preload("Roles.Auths").Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error
|
||||
err := cfg.DB().Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -56,14 +57,14 @@ func (h *handler) Get() (interface{}, error) {
|
||||
if userID != 0 {
|
||||
user := &models.User{}
|
||||
user.ID = uint(userID)
|
||||
return user, cfg.DB().Where(user).Preload("Scores").Preload("Roles.Auths").Preload("Favorites").First(user).Error
|
||||
return user, cfg.DB().Where(user).First(user).Error
|
||||
} else {
|
||||
users := make([]models.User, 10)
|
||||
skip, err := strconv.Atoi(h.Meta().Query("skip"))
|
||||
if err != nil || skip < 0 {
|
||||
skip = 0
|
||||
}
|
||||
if err := cfg.DB().Preload("Scores").Preload("Roles.Auths").Offset(skip).Find(&users).Error; err != nil {
|
||||
if err := cfg.DB().Offset(skip).Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
@ -73,16 +74,15 @@ func (h *handler) Get() (interface{}, error) {
|
||||
// Post register user
|
||||
func (h *handler) Post() (interface{}, error) {
|
||||
self := &models.App{}
|
||||
self.ID = cfg.CFG.APPID
|
||||
self.UUID = cfg.CFG.APPUUID
|
||||
err := cfg.DB().Where(self).First(self).Error
|
||||
if err != nil {
|
||||
return nil, oerr.DBErr.Attach(err)
|
||||
}
|
||||
if !self.EnableRegister {
|
||||
if !self.EnableRegister && !h.Payload.GetAuth(auth.User, "").CanCreate() {
|
||||
return nil, oerr.NoAuth.AttachStr("register disabled")
|
||||
}
|
||||
var userdata = struct {
|
||||
UUID string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
@ -120,27 +120,10 @@ func (h *handler) Post() (interface{}, error) {
|
||||
if err := tx.Create(&h.User).Error; err != nil {
|
||||
return oerr.ResourceDuplicated
|
||||
}
|
||||
err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID)
|
||||
err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID, models.AUOK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if userdata.UUID != self.UUID && userdata.UUID != "" {
|
||||
target := &models.App{}
|
||||
target.UUID = userdata.UUID
|
||||
err = tx.Where(target).First(target).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.EnableRegister {
|
||||
err := app.AddUser(tx, target.ID, h.User.ID, target.InitRoleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -169,11 +152,10 @@ func (h *handler) Patch() (interface{}, error) {
|
||||
} else {
|
||||
target.ID = uint(tempID)
|
||||
}
|
||||
tx := cfg.DB().Begin()
|
||||
if err := cfg.DB().Where(&target).First(&target).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if target.ID != h.Payload.ID {
|
||||
if target.ID != h.Payload.ID && h.Payload.GetAuth(auth.User, strconv.Itoa(int(target.ID))).CanUpdate() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
if len(opts.Password) >= 6 {
|
||||
@ -197,11 +179,9 @@ func (h *handler) Patch() (interface{}, error) {
|
||||
if opts.Status != "" {
|
||||
target.Status = opts.Status
|
||||
}
|
||||
if err := tx.Updates(&target).Error; err != nil {
|
||||
tx.Rollback()
|
||||
if err := cfg.DB().Updates(&target).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx.Commit()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -222,10 +202,6 @@ func (h *handler) Head() (interface{}, error) {
|
||||
if len(uid) == 0 || len(password) == 0 {
|
||||
return nil, oerr.ApiArgsError
|
||||
}
|
||||
appUUID := h.Meta().Query("uuid")
|
||||
if appUUID == "" {
|
||||
return nil, oerr.ApiArgsMissing.AttachStr("uuid")
|
||||
}
|
||||
h.User = new(models.User)
|
||||
uidType := h.Meta().Query("uid_type")
|
||||
switch uidType {
|
||||
@ -239,7 +215,7 @@ func (h *handler) Head() (interface{}, error) {
|
||||
h.User.Username = uid
|
||||
}
|
||||
target := &models.App{}
|
||||
target.UUID = appUUID
|
||||
target.UUID = cfg.CFG.APPUUID
|
||||
err = cfg.DB().Where(target).Find(target).Error
|
||||
if err != nil {
|
||||
return nil, oerr.DBErr.Attach(err)
|
||||
@ -261,17 +237,12 @@ func (h *handler) Head() (interface{}, error) {
|
||||
au.AppID = target.ID
|
||||
err = cfg.DB().Where(au).First(au).Error
|
||||
appID := target.ID
|
||||
h.Meta().SetHeader("content", target.UserRefreshUrl)
|
||||
if err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
appID = cfg.CFG.APPID
|
||||
h.Meta().SetHeader("content", "/app/"+target.UUID)
|
||||
} else if au.Disabled {
|
||||
return nil, err
|
||||
} else if au.Status != models.AUOK {
|
||||
return nil, oerr.DisableLogin
|
||||
}
|
||||
tokenStr, err := token.GetToken(h.User, appID)
|
||||
tokenStr, err := token.GetToken(h.User, appID, cfg.CFG.APPKey)
|
||||
if err != nil {
|
||||
log.HandlerErrs(err)
|
||||
return nil, oerr.Unknown.Attach(err)
|
||||
|
||||
@ -15,11 +15,11 @@ var CFG = &struct {
|
||||
Host string
|
||||
LoggerPath string
|
||||
LoggerLevel string
|
||||
APPID uint
|
||||
APPUUID string
|
||||
APPKey string
|
||||
TimeFormat string
|
||||
Debug bool
|
||||
EXEDir string
|
||||
MediaDir string
|
||||
DB struct {
|
||||
Type string
|
||||
Addr string
|
||||
@ -28,14 +28,15 @@ var CFG = &struct {
|
||||
DB string
|
||||
}
|
||||
}{
|
||||
APPID: 1,
|
||||
APPUUID: "jU5Jo5hM",
|
||||
APPKey: "cB43wF94MLTksyBK",
|
||||
AdminUser: "admin",
|
||||
Host: "0.0.0.0:4001",
|
||||
LoggerPath: "",
|
||||
LoggerLevel: "debug",
|
||||
TimeFormat: "2006/01/02 15:04:05",
|
||||
Debug: true,
|
||||
EXEDir: "./",
|
||||
MediaDir: "/Users/light/test/media/",
|
||||
DB: struct {
|
||||
Type string
|
||||
Addr string
|
||||
|
||||
@ -8,7 +8,10 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error {
|
||||
func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint, status models.AUStatus) error {
|
||||
if appID == 0 || userID == 0 {
|
||||
return oerr.FuncArgsError
|
||||
}
|
||||
au := &models.AppUser{}
|
||||
au.AppID = appID
|
||||
au.UserID = userID
|
||||
@ -17,19 +20,25 @@ func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error {
|
||||
return oerr.ResourceDuplicated
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
au.Status = status
|
||||
err = tx.Create(au).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = auth.BindUserRole(tx, userID, roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
if roleID > 0 {
|
||||
err = auth.BindUserRole(tx, userID, roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Model(&models.App{}).Where("id = ?", appID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error
|
||||
}
|
||||
return err
|
||||
}
|
||||
func EnableUser(tx *gorm.DB, appID uint, userID uint) error {
|
||||
if appID == 0 || userID == 0 {
|
||||
return oerr.FuncArgsError
|
||||
}
|
||||
au := &models.AppUser{}
|
||||
au.AppID = appID
|
||||
au.UserID = userID
|
||||
@ -37,12 +46,18 @@ func EnableUser(tx *gorm.DB, appID uint, userID uint) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Where(au).Update("disabled", false).Error
|
||||
if au.Status != models.AUOK {
|
||||
return tx.Where(au).Update("status", models.AUOK).Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DisableUser(tx *gorm.DB, appID uint, userID uint) error {
|
||||
if appID == 0 || userID == 0 {
|
||||
return oerr.FuncArgsError
|
||||
}
|
||||
au := &models.AppUser{}
|
||||
au.AppID = appID
|
||||
au.UserID = userID
|
||||
return tx.Where(au).Update("disabled", true).Error
|
||||
return tx.Where(au).Update("status", models.AUDisable).Error
|
||||
}
|
||||
|
||||
@ -3,10 +3,12 @@ package base
|
||||
import (
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/libs/tools"
|
||||
"errors"
|
||||
"github.com/json-iterator/go"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
"github.com/veypi/utils/log"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@ -14,6 +16,41 @@ import (
|
||||
|
||||
var json = jsoniter.ConfigFastest
|
||||
|
||||
func JSONResponse(m OneBD.Meta, data interface{}, err error) {
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = oerr.ResourceNotExist
|
||||
}
|
||||
}
|
||||
if m.Method() == rfc.MethodHead {
|
||||
if err != nil {
|
||||
m.SetHeader("status", "0")
|
||||
m.SetHeader("code", strconv.Itoa(int(oerr.OfType(err.Error()))))
|
||||
m.SetHeader("err", err.Error())
|
||||
} else {
|
||||
m.SetHeader("status", "1")
|
||||
}
|
||||
return
|
||||
}
|
||||
res := map[string]interface{}{
|
||||
"status": 1,
|
||||
}
|
||||
if err != nil {
|
||||
res["status"] = 0
|
||||
res["code"] = oerr.OfType(err.Error())
|
||||
res["err"] = err.Error()
|
||||
} else {
|
||||
res["status"] = 1
|
||||
res["content"] = data
|
||||
}
|
||||
p, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("encode json data error")
|
||||
return
|
||||
}
|
||||
_, _ = m.Write(p)
|
||||
}
|
||||
|
||||
type ApiHandler struct {
|
||||
OneBD.BaseHandler
|
||||
UserHandler
|
||||
@ -24,29 +61,12 @@ func (h *ApiHandler) Init(m OneBD.Meta) error {
|
||||
}
|
||||
|
||||
func (h *ApiHandler) OnResponse(data interface{}) {
|
||||
if h.Meta().Method() == rfc.MethodHead {
|
||||
h.Meta().SetHeader("status", "1")
|
||||
return
|
||||
}
|
||||
p, err := json.Marshal(map[string]interface{}{"status": 1, "content": data})
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("encode json data error")
|
||||
return
|
||||
}
|
||||
h.Meta().Write(p)
|
||||
JSONResponse(h.Meta(), data, nil)
|
||||
}
|
||||
|
||||
func (h *ApiHandler) OnError(err error) {
|
||||
log.WithNoCaller.Warn().Err(err).Msg(h.Meta().RequestPath())
|
||||
msg := err.Error()
|
||||
if h.Meta().Method() == rfc.MethodHead {
|
||||
h.Meta().SetHeader("status", "0")
|
||||
h.Meta().SetHeader("code", strconv.Itoa(int(oerr.OfType(msg))))
|
||||
h.Meta().SetHeader("err", msg)
|
||||
} else {
|
||||
p, _ := json.Marshal(map[string]interface{}{"status": 0, "code": oerr.OfType(msg), "err": msg})
|
||||
h.Meta().Write(p)
|
||||
}
|
||||
JSONResponse(h.Meta(), nil, err)
|
||||
}
|
||||
|
||||
var ioNumLimit = make(map[string]time.Time)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/libs/token"
|
||||
"OneAuth/models"
|
||||
@ -26,7 +27,7 @@ func (a *UserHandler) ParsePayload(m OneBD.Meta) error {
|
||||
if tokenStr == "" {
|
||||
return oerr.NotLogin
|
||||
}
|
||||
ok, err := token.ParseToken(tokenStr, a.Payload)
|
||||
ok, err := token.ParseToken(tokenStr, a.Payload, cfg.CFG.APPKey)
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package oerr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
)
|
||||
@ -42,11 +41,13 @@ type Code uint
|
||||
- 9 : 本不可能发生的错误,例如被人攻击导致数据异常产生的逻辑错误
|
||||
|
||||
*/
|
||||
|
||||
// Unknown error
|
||||
const (
|
||||
Unknown Code = 0
|
||||
)
|
||||
const (
|
||||
// 2 数据库错误
|
||||
// DBErr 2 数据库错误
|
||||
// -1 系统错误
|
||||
// -2 数据读写错误
|
||||
DBErr Code = 20001
|
||||
@ -56,10 +57,13 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
// 3
|
||||
// LogicErr 3 系统内逻辑错误
|
||||
LogicErr Code = 30000
|
||||
AppNotJoin Code = 30001
|
||||
)
|
||||
|
||||
const (
|
||||
// NotLogin
|
||||
// 4 权限类型错误
|
||||
// 1: 登录权限
|
||||
// 2: 资源操作权限
|
||||
@ -130,6 +134,8 @@ var codeMap = map[Code]string{
|
||||
NoAuth: "no auth to access",
|
||||
AccessErr: "access error",
|
||||
AccessTooFast: "access too fast",
|
||||
LogicErr: "logic error",
|
||||
AppNotJoin: "not join in app",
|
||||
}
|
||||
|
||||
func (c Code) Error() string {
|
||||
@ -144,7 +150,7 @@ func (c Code) String() string {
|
||||
return codeMap[Unknown]
|
||||
}
|
||||
|
||||
// 附加错误详细原因
|
||||
// Attach 附加错误详细原因
|
||||
func (c Code) Attach(errs ...error) (e error) {
|
||||
e = c
|
||||
for _, err := range errs {
|
||||
@ -195,16 +201,3 @@ func (w *wrapErr) Error() string {
|
||||
func (w *wrapErr) UnWrap() error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
func CheckMultiErr(errs ...error) error {
|
||||
msg := ""
|
||||
for _, e := range errs {
|
||||
if e != nil {
|
||||
msg += e.Error() + "\n"
|
||||
}
|
||||
}
|
||||
if msg != "" {
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"OneAuth/libs/key"
|
||||
"OneAuth/models"
|
||||
"github.com/veypi/utils/jwt"
|
||||
)
|
||||
@ -16,9 +15,8 @@ type simpleAuth struct {
|
||||
// TODO:: roles 是否会造成token过大 ?
|
||||
type PayLoad struct {
|
||||
jwt.Payload
|
||||
ID uint `json:"id"`
|
||||
AppID uint `json:"app_id"`
|
||||
Auth map[uint]*simpleAuth `json:"auth"`
|
||||
ID uint `json:"id"`
|
||||
Auth map[uint]*simpleAuth `json:"auth"`
|
||||
}
|
||||
|
||||
// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖
|
||||
@ -49,11 +47,10 @@ func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) models.Auth
|
||||
return res
|
||||
}
|
||||
|
||||
func GetToken(u *models.User, appID uint) (string, error) {
|
||||
func GetToken(u *models.User, appID uint, key string) (string, error) {
|
||||
payload := &PayLoad{
|
||||
ID: u.ID,
|
||||
AppID: appID,
|
||||
Auth: map[uint]*simpleAuth{},
|
||||
ID: u.ID,
|
||||
Auth: map[uint]*simpleAuth{},
|
||||
}
|
||||
for _, a := range u.GetAuths() {
|
||||
if appID == a.AppID {
|
||||
@ -64,9 +61,9 @@ func GetToken(u *models.User, appID uint) (string, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return jwt.GetToken(payload, []byte(key.User(payload.ID, payload.AppID)))
|
||||
return jwt.GetToken(payload, []byte(key))
|
||||
}
|
||||
|
||||
func ParseToken(token string, payload *PayLoad) (bool, error) {
|
||||
return jwt.ParseToken(token, payload, []byte(key.User(payload.ID, payload.AppID)))
|
||||
func ParseToken(token string, payload *PayLoad, key string) (bool, error) {
|
||||
return jwt.ParseToken(token, payload, []byte(key))
|
||||
}
|
||||
|
||||
21
main.go
21
main.go
@ -7,7 +7,6 @@ import (
|
||||
"github.com/veypi/utils/cmd"
|
||||
"github.com/veypi/utils/log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const Version = "v0.1.0"
|
||||
@ -35,21 +34,6 @@ func main() {
|
||||
Value: cfg.CFG.LoggerPath,
|
||||
Destination: &cfg.CFG.LoggerPath,
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "id",
|
||||
Value: cfg.CFG.APPID,
|
||||
Destination: &cfg.CFG.APPID,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "key",
|
||||
Value: cfg.CFG.APPKey,
|
||||
Destination: &cfg.CFG.APPKey,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "exe_dir",
|
||||
Value: cfg.CFG.EXEDir,
|
||||
Destination: &cfg.CFG.EXEDir,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: cfg.CFG.Host,
|
||||
@ -72,11 +56,6 @@ func main() {
|
||||
srv.SetStopFunc(func() {
|
||||
})
|
||||
app.Before = func(c *cli.Context) error {
|
||||
var err error
|
||||
cfg.CFG.EXEDir, err = filepath.Abs(cfg.CFG.EXEDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.CFG.Debug {
|
||||
cfg.CFG.LoggerLevel = "debug"
|
||||
}
|
||||
|
||||
@ -42,12 +42,22 @@ type App struct {
|
||||
Wx *Wechat `json:"wx" gorm:"foreignkey:AppID;references:ID"`
|
||||
}
|
||||
|
||||
type AUStatus string
|
||||
|
||||
const (
|
||||
AUOK AUStatus = "ok"
|
||||
AUDisable AUStatus = "disabled"
|
||||
AUApply AUStatus = "apply"
|
||||
AUDeny AUStatus = "deny"
|
||||
)
|
||||
|
||||
type AppUser struct {
|
||||
BaseModel
|
||||
AppID uint `json:"app_id"`
|
||||
UserID uint `json:"user_id"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Status string `json:"status"`
|
||||
AppID uint `json:"app_id"`
|
||||
APP *App `json:"app"`
|
||||
UserID uint `json:"user_id"`
|
||||
User *User `json:"user"`
|
||||
Status AUStatus `json:"status"`
|
||||
}
|
||||
|
||||
type Wechat struct {
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@veypi/one-icon": "2",
|
||||
"@veypi/one-icon": "2.0.5",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^0.24.0",
|
||||
"js-base64": "^3.7.2",
|
||||
"vue": "^3.2.16",
|
||||
@ -18,6 +19,7 @@
|
||||
"@tailwindcss/postcss7-compat": "^2.1.0",
|
||||
"@vitejs/plugin-vue": "^1.9.3",
|
||||
"autoprefixer": "^9.8.8",
|
||||
"less": "^4.1.2",
|
||||
"naive-ui": "^2.19.11",
|
||||
"postcss": "^7.0.39",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",
|
||||
|
||||
File diff suppressed because one or more lines are too long
135
oaf/src/App.vue
135
oaf/src/App.vue
@ -1,59 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
import { onBeforeMount, ref} from 'vue'
|
||||
import util from './libs/util'
|
||||
import {store} from "./store";
|
||||
import {useOsTheme} from 'naive-ui'
|
||||
|
||||
const osThemeRef = useOsTheme()
|
||||
|
||||
onBeforeMount(() => {
|
||||
util.title("统一认证")
|
||||
store.dispatch('fetchSelf')
|
||||
store.commit('setTheme', osThemeRef.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :theme="store.getters.GetTheme" class="h-full w-full">
|
||||
<n-layout class="h-full w-full font-sans select-none">
|
||||
<n-layout has-sider style="height: calc(100% - 24px)">
|
||||
<n-layout-sider
|
||||
class="h-full"
|
||||
collapse-mode="transform"
|
||||
:collapsed-width="0"
|
||||
:width="120"
|
||||
show-trigger="bar"
|
||||
content-style="padding: 24px;"
|
||||
bordered
|
||||
default-collapsed
|
||||
>
|
||||
-
|
||||
</n-layout-sider>
|
||||
<n-layout>
|
||||
<n-layout-header bordered style="height: 64px;line-height: 64px;">
|
||||
{{ osThemeRef }}
|
||||
<one-icon @click="store.dispatch('changeTheme')" class="float-right" style="font-size: 36px; margin: 14px">
|
||||
{{ store.getters.IsDark ? 'Daytimemode' : 'nightmode-fill' }}
|
||||
<n-config-provider :theme-overrides="Theme.overrides" :locale="zhCN" :date-locale="dateZhCN"
|
||||
:theme="Theme">
|
||||
<n-layout class="font-sans select-none">
|
||||
<n-layout>
|
||||
<n-layout-header class="pr-5" bordered style="height: 64px;line-height: 64px;">
|
||||
<div class="inline-block float-left h-full">
|
||||
<one-icon color="#000" class="inline-block" @click="$router.push('/')" style="font-size: 48px;margin:8px;color:aqua">
|
||||
glassdoor
|
||||
</one-icon>
|
||||
</n-layout-header>
|
||||
<n-layout style="height: calc(100% - 64px)">
|
||||
<router-view class="h-full w-full"></router-view>
|
||||
</div>
|
||||
<div class="inline-block float-left h-full" style="margin-left: 10px">
|
||||
<n-h6 prefix="bar" align-text><n-text type="primary">统一认证系统</n-text></n-h6>
|
||||
</div>
|
||||
<div v-if="store.state.user.ready" class="inline-block h-full float-right flex justify-center items-center">
|
||||
<avatar></avatar>
|
||||
</div>
|
||||
<div class="inline-block float-right h-full px-3">
|
||||
<fullscreen v-model="isFullScreen" class="header-icon">fullscreen</fullscreen>
|
||||
<div class="header-icon">
|
||||
<one-icon @click="ChangeTheme">
|
||||
{{ IsDark ? 'Daytimemode' : 'nightmode-fill' }}
|
||||
</one-icon>
|
||||
</div>
|
||||
</div>
|
||||
</n-layout-header>
|
||||
<n-layout has-sider style="height: calc(100vh - 88px)">
|
||||
<n-layout-sider
|
||||
collapse-mode="transform"
|
||||
:collapsed-width="0"
|
||||
:width="120"
|
||||
show-trigger="bar"
|
||||
content-style="padding: 24px;"
|
||||
bordered
|
||||
default-collapsed
|
||||
:native-scrollbar="false"
|
||||
>
|
||||
-
|
||||
</n-layout-sider>
|
||||
<n-layout class="main" :native-scrollbar="false">
|
||||
<n-message-provider>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight">
|
||||
<component class="animate__animated animate__400ms" :is="Component" style="margin: 10px; min-height: calc(100vh - 108px)"
|
||||
></component>
|
||||
</transition>
|
||||
</router-view>
|
||||
</n-message-provider>
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
<n-layout-footer style="height: 24px;line-height: 24px" class="flex justify-around px-3 text-gray-500 text-xs">
|
||||
<span class="hover:text-black cursor-pointer">关于OA</span>
|
||||
<n-layout-footer bordered style="height: 24px;line-height: 24px"
|
||||
class="flex justify-around px-3 text-gray-500 text-xs">
|
||||
<span class="hover:text-black cursor-pointer" @click="$router.push({name: 'about'})">关于OA</span>
|
||||
<span class="hover:text-black cursor-pointer">使用须知</span>
|
||||
<span class="hover:text-black cursor-pointer">
|
||||
<span class="hover:text-black cursor-pointer" @click="goto('https://veypi.com')">
|
||||
©2021 veypi
|
||||
</span>
|
||||
</n-layout-footer>
|
||||
</n-layout>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
import {onBeforeMount, ref} from 'vue'
|
||||
import util from './libs/util'
|
||||
import {useStore} from "./store";
|
||||
import {Theme, IsDark, ChangeTheme} from "./theme";
|
||||
import {zhCN, dateZhCN} from 'naive-ui'
|
||||
import avatar from "./components/avatar";
|
||||
import fullscreen from './components/fullscreen'
|
||||
import Fullscreen from "./components/fullscreen/fullscreen.vue";
|
||||
|
||||
let isFullScreen = ref(false)
|
||||
let store = useStore()
|
||||
|
||||
onBeforeMount(() => {
|
||||
util.title("统一认证")
|
||||
store.dispatch('fetchSelf')
|
||||
store.dispatch('user/fetchUserData')
|
||||
})
|
||||
|
||||
let goto = (url: any) => {
|
||||
window.open(url, "_blank")
|
||||
}
|
||||
|
||||
let collapsed = ref(true)
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less">
|
||||
.animate__400ms {
|
||||
--animate-duration: 400ms;
|
||||
}
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
@ -73,6 +113,12 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
margin: 20px 10px 20px 10px;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@ -82,6 +128,9 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none; /* Chrome Safari */
|
||||
}
|
||||
|
||||
@ -13,19 +13,19 @@ function baseRequests(url: string, method: any = 'GET', query: any, data: any, s
|
||||
auth_token: localStorage.auth_token
|
||||
}
|
||||
}).then((res: any) => {
|
||||
if ('auth_token' in res.headers) {
|
||||
localStorage.auth_token = res.headers.auth_token
|
||||
}
|
||||
if (method === 'HEAD') {
|
||||
success(res.headers)
|
||||
} else {
|
||||
success(res.data)
|
||||
}
|
||||
})
|
||||
if ('auth_token' in res.headers) {
|
||||
localStorage.auth_token = res.headers.auth_token
|
||||
}
|
||||
if (method === 'HEAD') {
|
||||
success(res.headers)
|
||||
} else {
|
||||
success(res.data)
|
||||
}
|
||||
})
|
||||
.catch((e: any) => {
|
||||
if (e.response && e.response.status === 401) {
|
||||
console.log(e)
|
||||
store.dispatch('handleLogout')
|
||||
store.commit('user/logout')
|
||||
return
|
||||
}
|
||||
console.log(e)
|
||||
|
||||
@ -10,7 +10,6 @@ import {store} from '../store'
|
||||
import {Base64} from 'js-base64'
|
||||
|
||||
|
||||
|
||||
export type SuccessFunction<T> = (e: any) => void;
|
||||
export type FailedFunction<T> = (e: any) => void;
|
||||
|
||||
@ -34,15 +33,15 @@ class Interface {
|
||||
const newFail = function (data: any) {
|
||||
if (data && data.code === 40001) {
|
||||
// no login
|
||||
store.dispatch('handleLogout')
|
||||
store.commit('user/logout')
|
||||
return
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||
// @ts-ignore
|
||||
if (data && data.code > 0 && Code[data.code]) {
|
||||
if (data && data.code && Code[data.code]) {
|
||||
}
|
||||
if (fail) {
|
||||
fail(data)
|
||||
fail(data.err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +53,7 @@ class Interface {
|
||||
} else {
|
||||
newFail(data)
|
||||
if (data.code === 41001) {
|
||||
store.dispatch('handleLogout')
|
||||
store.commit('user/logout')
|
||||
// bus.$emit('log_out')
|
||||
}
|
||||
}
|
||||
@ -92,6 +91,15 @@ const user = {
|
||||
uuid: uuid,
|
||||
password: Base64.encode(password)
|
||||
})
|
||||
},
|
||||
get(id: number) {
|
||||
return new Interface(ajax.get, this.local + id)
|
||||
},
|
||||
list() {
|
||||
return new Interface(ajax.get, this.local)
|
||||
},
|
||||
update(id: number, props: any) {
|
||||
return new Interface(ajax.patch, this.local + id, props)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
oaf/src/assets/icon.js
Normal file
1
oaf/src/assets/icon.js
Normal file
File diff suppressed because one or more lines are too long
29
oaf/src/components/app.vue
Normal file
29
oaf/src/components/app.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="core rounded-2xl p-3">
|
||||
<div class="grid gap-4 grid-cols-5">
|
||||
<div class="col-span-2">
|
||||
<n-avatar @click="$router.push({name: 'app', params: {uuid: core.uuid}})" round :size="80" :src="core.icon">
|
||||
{{ core.icon ? '' : core.name }}
|
||||
</n-avatar>
|
||||
</div>
|
||||
<div class="col-span-3 grid grid-cols-1 items-center text-left">
|
||||
<div class="h-10 flex items-center text-2xl italic font-bold">{{ core.name }}</div>
|
||||
<div class="select-all">{{ core.uuid }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<textarea disabled style="background: none;border: none" class="focus:outline-none w-full">{{core.des}}</textarea>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import {defineProps} from "vue";
|
||||
|
||||
let props = defineProps<{
|
||||
core: any
|
||||
}>()
|
||||
</script>
|
||||
<style scoped>
|
||||
.core {
|
||||
width: 256px;
|
||||
background: #2c3e50;
|
||||
}
|
||||
</style>
|
||||
61
oaf/src/components/avatar/avatar.vue
Normal file
61
oaf/src/components/avatar/avatar.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<base_frame style="line-height:40px" v-model="shown" :isDark="IsDark">
|
||||
<div class="flex">
|
||||
<n-avatar :src="$store.state.user.icon" round></n-avatar>
|
||||
</div>
|
||||
<template v-slot:main>
|
||||
<div style="height: 100%">
|
||||
<div style="height: calc(100% - 50px)">
|
||||
<div class="w-full px-3">
|
||||
<div class="h-16 flex justify-between items-center">
|
||||
<span style="color: #777">我的账户</span>
|
||||
<span @click="$router.push({name: 'user_setting'});shown=false" class="cursor-pointer"
|
||||
style="color:#f36828">账户中心</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4 h-20">
|
||||
<div class="flex items-center justify-center">
|
||||
<n-avatar size="50" :src="$store.state.user.icon" round></n-avatar>
|
||||
</div>
|
||||
<div class="col-span-2 text-xs grid grid-cols-1 items-center" style="">
|
||||
<span>昵称:    {{ $store.state.user.nickname }}</span>
|
||||
<span>账户:    {{ $store.state.user.username }}</span>
|
||||
<span>邮箱:    {{ $store.state.user.email }}</span>
|
||||
</div>
|
||||
<div class="">123</div>
|
||||
</div>
|
||||
<hr class="mt-10" style="border:none;border-top:1px solid #777;">
|
||||
</div>
|
||||
</div>
|
||||
<hr style="border:none;border-top:2px solid #777;">
|
||||
<div style="height: 48px">
|
||||
<div @click="$store.commit('user/logout')"
|
||||
class="w-full h-full flex justify-center items-center cursor-pointer transition duration-500 ease-in-out transform hover:scale-125">
|
||||
<one-icon :color="IsDark?'#eee': '#333'" class="inline-block" style="font-size: 24px;">
|
||||
logout
|
||||
</one-icon>
|
||||
<div>
|
||||
退出登录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</base_frame>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import base_frame from './frame.vue'
|
||||
import {IsDark} from '../../theme'
|
||||
import {ref} from "vue";
|
||||
|
||||
let shown = ref(false)
|
||||
|
||||
function asd(e) {
|
||||
console.log([e, shown.value])
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
82
oaf/src/components/avatar/frame.vue
Normal file
82
oaf/src/components/avatar/frame.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<div @click="setValue(true)">
|
||||
<slot>
|
||||
</slot>
|
||||
</div>
|
||||
<div @click.self="setValue(false)" class="core" style="height: 100vh;width: 100vw;" v-if="props.modelValue">
|
||||
<div style="height: 100%; width: 300px" class="core-right">
|
||||
<transition appear enter-active-class="animate__slideInRight">
|
||||
<div class="right-title animate__animated animate__faster">
|
||||
<slot name="title"></slot>
|
||||
<div class="flex items-center float-right h-full px-1">
|
||||
<one-icon @click="setValue(false)" color="#fff" style="font-size: 24px">close</one-icon>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<div class="right-main">
|
||||
<transition appear enter-active-class="animate__slideInDown">
|
||||
<div class="right-main-core animate__animated animate__faster"
|
||||
:style="{'background': props.isDark ? '#222': '#eee'}">
|
||||
<slot name="main"></slot>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, defineEmits, watch} from "vue";
|
||||
|
||||
let emits = defineEmits<{
|
||||
(e: 'update:modelValue', v: boolean): void
|
||||
}>()
|
||||
let props = defineProps<{
|
||||
isDark: boolean,
|
||||
modelValue: boolean
|
||||
}>()
|
||||
|
||||
function setValue(b: boolean) {
|
||||
emits('update:modelValue', b)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.core {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.core-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.right-main {
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.right-main-core {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
--animate-duration: 400ms;
|
||||
}
|
||||
|
||||
.right-title {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
background: linear-gradient(90deg, #f74d22, #fa9243);
|
||||
}
|
||||
</style>
|
||||
3
oaf/src/components/avatar/index.ts
Normal file
3
oaf/src/components/avatar/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import avatar from './avatar.vue'
|
||||
|
||||
export default avatar
|
||||
10
oaf/src/components/frame.vue
Normal file
10
oaf/src/components/frame.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
71
oaf/src/components/fullscreen/fullscreen.vue
Normal file
71
oaf/src/components/fullscreen/fullscreen.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div @click="handleFullscreen">
|
||||
<one-icon>{{ props.modelValue ? 'fullscreen-exit' : 'fullscreen' }}</one-icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
import {defineEmits, onMounted, defineProps} from "vue";
|
||||
|
||||
let emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: boolean): void
|
||||
}>()
|
||||
|
||||
let props = defineProps<{
|
||||
modelValue: boolean
|
||||
}>()
|
||||
|
||||
function handleFullscreen() {
|
||||
let main = document.body
|
||||
if (props.modelValue) {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen()
|
||||
} else if (document.webkitCancelFullScreen) {
|
||||
document.webkitCancelFullScreen()
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen()
|
||||
}
|
||||
} else {
|
||||
if (main.requestFullscreen) {
|
||||
main.requestFullscreen()
|
||||
} else if (main.mozRequestFullScreen) {
|
||||
main.mozRequestFullScreen()
|
||||
} else if (main.webkitRequestFullScreen) {
|
||||
main.webkitRequestFullScreen()
|
||||
} else if (main.msRequestFullscreen) {
|
||||
main.msRequestFullscreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
let isFullscreen =
|
||||
document.fullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.fullScreen ||
|
||||
document.mozFullScreen ||
|
||||
document.webkitIsFullScreen
|
||||
isFullscreen = !!isFullscreen
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
emit('update:modelValue', !props.modelValue)
|
||||
})
|
||||
document.addEventListener('mozfullscreenchange', () => {
|
||||
emit('update:modelValue', !props.modelValue)
|
||||
})
|
||||
document.addEventListener('webkitfullscreenchange', () => {
|
||||
emit('update:modelValue', !props.modelValue)
|
||||
})
|
||||
document.addEventListener('msfullscreenchange', () => {
|
||||
emit('update:modelValue', !props.modelValue)
|
||||
})
|
||||
emit('update:modelValue', isFullscreen)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
2
oaf/src/components/fullscreen/index.ts
Normal file
2
oaf/src/components/fullscreen/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import fullscreen from './fullscreen.vue'
|
||||
export default fullscreen
|
||||
@ -27,6 +27,9 @@ const util = {
|
||||
document.cookie =
|
||||
name + '=' + escape(value) + ';expires=' + exp.toLocaleString()
|
||||
},
|
||||
getToken() {
|
||||
return localStorage.auth_token
|
||||
},
|
||||
checkLogin() {
|
||||
// return parseInt(this.getCookie('stat')) === 1
|
||||
return Boolean(localStorage.auth_token)
|
||||
|
||||
@ -6,12 +6,14 @@ import OneIcon from '@veypi/one-icon'
|
||||
import naive from 'naive-ui'
|
||||
import './index.css'
|
||||
import {Api} from './api'
|
||||
import './assets/icon.js'
|
||||
import 'animate.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(Api)
|
||||
app.use(naive)
|
||||
app.use(OneIcon, {href: './icon.js'})
|
||||
app.use(OneIcon)
|
||||
app.use(router)
|
||||
app.use(store, key)
|
||||
app.mount('#app')
|
||||
|
||||
@ -22,12 +22,25 @@ const router = createRouter({
|
||||
component: () => import('../views/home.vue')
|
||||
},
|
||||
{
|
||||
path: '/app',
|
||||
path: '/app/:uuid?',
|
||||
name: 'app',
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: () => import('../views/demo.vue')
|
||||
component: () => import('../views/app.vue')
|
||||
},
|
||||
{
|
||||
path: '/user/setting',
|
||||
name: 'user_setting',
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
},
|
||||
component: () => import('../views/user_setting.vue')
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
component: () => import('../views/about.vue')
|
||||
},
|
||||
{
|
||||
path: '/wx',
|
||||
|
||||
@ -1,58 +1,45 @@
|
||||
import {InjectionKey} from 'vue'
|
||||
import {createStore, useStore as baseUseStore, Store} from 'vuex'
|
||||
import api from "../api";
|
||||
import router from "../router";
|
||||
import {darkTheme} from 'naive-ui'
|
||||
import {User, UserState} from './user'
|
||||
|
||||
export interface State {
|
||||
export interface State extends Object {
|
||||
oauuid: string
|
||||
user: object
|
||||
theme: string
|
||||
|
||||
user: UserState
|
||||
apps: []
|
||||
}
|
||||
|
||||
export const key: InjectionKey<Store<State>> = Symbol()
|
||||
|
||||
export const store = createStore<State>({
|
||||
modules: {
|
||||
user: User
|
||||
},
|
||||
// @ts-ignore
|
||||
state: {
|
||||
theme: 'light',
|
||||
oauuid: '',
|
||||
user: {}
|
||||
},
|
||||
getters: {
|
||||
IsDark(state: any) {
|
||||
return state.theme === 'dark'
|
||||
},
|
||||
GetTheme(state: any, getters) {
|
||||
return getters.IsDark ? darkTheme : null
|
||||
}
|
||||
apps: []
|
||||
},
|
||||
getters: {},
|
||||
mutations: {
|
||||
setOA(state: any, data: any) {
|
||||
state.oauuid = data.uuid
|
||||
},
|
||||
setTheme(state: any, t: string) {
|
||||
state.theme = t
|
||||
setApps(state: State, data: any) {
|
||||
state.apps = data
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
changeTheme(context) {
|
||||
if (context.getters.IsDark) {
|
||||
context.commit('setTheme', 'light')
|
||||
} else {
|
||||
context.commit('setTheme', 'dark')
|
||||
}
|
||||
},
|
||||
fetchSelf({commit}) {
|
||||
api.app.self().Start(d => {
|
||||
commit('setOA', d)
|
||||
})
|
||||
},
|
||||
handleLogout() {
|
||||
localStorage.removeItem('auth_token')
|
||||
router.push({name: 'login'})
|
||||
fetchApps({commit}) {
|
||||
api.app.list().Start(e => {
|
||||
commit('setApps', e)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
73
oaf/src/store/user.ts
Normal file
73
oaf/src/store/user.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import {Module} from "vuex";
|
||||
import api from "../api";
|
||||
import util from '../libs/util'
|
||||
import {Base64} from 'js-base64'
|
||||
import {State} from './index'
|
||||
import router from "../router";
|
||||
|
||||
export interface UserState {
|
||||
id: number
|
||||
username: string
|
||||
nickname: string
|
||||
phone: string
|
||||
icon: string
|
||||
email: string
|
||||
ready: boolean
|
||||
auth: [auth?]
|
||||
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface auth {
|
||||
rid: string
|
||||
ruid: string
|
||||
level: number
|
||||
}
|
||||
|
||||
export const User: Module<UserState, State> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
id: 0,
|
||||
username: '',
|
||||
nickname: '',
|
||||
phone: '',
|
||||
icon: '',
|
||||
email: '',
|
||||
auth: [],
|
||||
ready: false
|
||||
},
|
||||
mutations: {
|
||||
setBase(state: UserState, data: any) {
|
||||
state.id = data.id
|
||||
state.icon = data.icon
|
||||
state.username = data.username
|
||||
state.nickname = data.nickname
|
||||
state.phone = data.phone
|
||||
state.email = data.email
|
||||
state.ready = true
|
||||
},
|
||||
setAuth(state: UserState, data: any) {
|
||||
state.auth = data
|
||||
},
|
||||
logout(state: UserState) {
|
||||
state.ready = false
|
||||
localStorage.removeItem('auth_token')
|
||||
router.push({name: 'login'})
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchUserData(context) {
|
||||
let token = util.getToken()?.split('.');
|
||||
if (!token || token.length !== 3) {
|
||||
return false
|
||||
}
|
||||
let data = JSON.parse(Base64.decode(token[1]))
|
||||
if (data.id > 0) {
|
||||
context.commit('setAuth', data.auth)
|
||||
api.user.get(data.id).Start(e => {
|
||||
context.commit('setBase', e)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
oaf/src/theme/index.ts
Normal file
72
oaf/src/theme/index.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import {darkTheme} from 'naive-ui/lib/themes'
|
||||
import {BuiltInGlobalTheme} from 'naive-ui/lib/themes/interface'
|
||||
import {lightTheme} from 'naive-ui/lib/themes/light'
|
||||
import {ref} from 'vue'
|
||||
import {useOsTheme, GlobalThemeOverrides} from 'naive-ui'
|
||||
|
||||
interface builtIn extends BuiltInGlobalTheme {
|
||||
overrides: GlobalThemeOverrides
|
||||
me: {
|
||||
lightBox: string,
|
||||
lightBoxShadow: string
|
||||
}
|
||||
}
|
||||
|
||||
let light = lightTheme as builtIn
|
||||
let dark = darkTheme as builtIn
|
||||
let intputNone = {
|
||||
color: 'url(0) no-repeat',
|
||||
colorFocus: 'url(0) no-repeat',
|
||||
colorFocusWarning: 'url(0) no-repeat',
|
||||
colorFocusError: 'url(0) no-repeat'
|
||||
}
|
||||
light.overrides = {
|
||||
Input: Object.assign({}, intputNone)
|
||||
}
|
||||
dark.overrides = {
|
||||
Input: Object.assign({
|
||||
border: '1px solid #aaa'
|
||||
}, intputNone)
|
||||
}
|
||||
light.common.cardColor = '#f4f4f4'
|
||||
light.common.bodyColor = '#eee'
|
||||
dark.common.bodyColor = '#2e2e2e'
|
||||
light.me = {
|
||||
lightBox: '#f4f4f4',
|
||||
lightBoxShadow: '18px 18px 36px #c6c6c6, -18px -18px 36px #fff'
|
||||
}
|
||||
|
||||
dark.me = {
|
||||
lightBox: '#2e2e2e',
|
||||
lightBoxShadow: '21px 21px 42px #272727, -21px -21px 42px #353535'
|
||||
}
|
||||
export const OsThemeRef = useOsTheme()
|
||||
|
||||
let theme = 'light'
|
||||
|
||||
export let Theme = ref(light)
|
||||
|
||||
export let IsDark = ref(false)
|
||||
|
||||
function change(t: string) {
|
||||
if (t === 'dark') {
|
||||
theme = 'dark'
|
||||
Theme.value = dark
|
||||
} else {
|
||||
theme = 'light'
|
||||
Theme.value = light
|
||||
}
|
||||
IsDark.value = theme === 'dark'
|
||||
}
|
||||
|
||||
export function ChangeTheme() {
|
||||
if (IsDark.value) {
|
||||
change('light')
|
||||
} else {
|
||||
change('dark')
|
||||
}
|
||||
}
|
||||
|
||||
if (OsThemeRef.value === 'dark') {
|
||||
change('dark')
|
||||
}
|
||||
12
oaf/src/views/about.vue
Normal file
12
oaf/src/views/about.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
about
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
28
oaf/src/views/app.vue
Normal file
28
oaf/src/views/app.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ uuid }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {computed, onMounted} from "vue";
|
||||
import api from "../api";
|
||||
|
||||
let route = useRoute()
|
||||
let router = useRouter()
|
||||
let uuid = computed(() => route.params.uuid)
|
||||
onMounted(() => {
|
||||
if (uuid.value === '') {
|
||||
router.push({name: '404', params: {path: route.path}})
|
||||
return
|
||||
}
|
||||
api.app.get(uuid.value as string).Start(e => {
|
||||
console.log(e)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -1,11 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center">
|
||||
<div class="flex items-center justify-center" v-for="(item, k) in apps" :key="k">
|
||||
<AppCard :core="item"></AppCard>
|
||||
</div>
|
||||
<div class="flex items-center justify-center" v-for="(item) in '1234567890'" :key="item">
|
||||
<AppCard :core="{}"></AppCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from "vue";
|
||||
import {onMounted, ref} from "vue";
|
||||
import api from "../api";
|
||||
import AppCard from '../components/app.vue'
|
||||
|
||||
let apps = ref([])
|
||||
|
||||
@ -15,7 +22,9 @@ function getApps() {
|
||||
})
|
||||
}
|
||||
|
||||
getApps()
|
||||
onMounted(() => {
|
||||
getApps()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,61 +1,98 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="p-3" style="">
|
||||
<n-form ref="formRef" label-placement="left">
|
||||
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]">
|
||||
<n-input v-model:value="data.username"></n-input>
|
||||
<div
|
||||
:style="{background:Theme.me.lightBox, 'box-shadow': Theme.me.lightBoxShadow}"
|
||||
class="px-10 pb-9 pt-28 rounded-xl w-96">
|
||||
<n-form label-width="70px" label-align="left" :model="data" ref="form_ref" label-placement="left" :rules="rules">
|
||||
<n-form-item required label="用户名" path="username">
|
||||
<n-input @keydown.enter="divs[1].focus()" :ref="el => {if (el)divs[0]=el}"
|
||||
v-model:value="data.username"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]">
|
||||
<n-input v-model:value="data.username"></n-input>
|
||||
<n-form-item required label="密码" path="password">
|
||||
<n-input @keydown.enter="login" :ref="el => {if (el) divs[1]=el}" v-model:value="data.password"
|
||||
type="password"></n-input>
|
||||
</n-form-item>
|
||||
<n-button @click="login">登录</n-button>
|
||||
<div class="flex justify-around mt-4">
|
||||
<n-button @click="login">登录</n-button>
|
||||
<n-button @click="router.push({name:'register'})">注册</n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref} from "vue";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {Theme} from "../theme";
|
||||
import {useMessage} from 'naive-ui'
|
||||
import api from "../api"
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {store} from "../store";
|
||||
|
||||
let formRef = ref(null)
|
||||
let msg = useMessage()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const divs = ref([])
|
||||
let form_ref = ref(null)
|
||||
let data = ref({
|
||||
username: null,
|
||||
password: null
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
let ruleInline = {
|
||||
let rules = {
|
||||
username: [
|
||||
(v: string) => !!v || 'required',
|
||||
(v: string) => (v && v.length >= 3 && v.length <= 16) || '长度要求3~16'
|
||||
],
|
||||
password: [
|
||||
(v: string) => !!v || 'required',
|
||||
(v: string) => (v && v.length >= 6 && v.length <= 16) || '长度要求6~16'
|
||||
]
|
||||
}
|
||||
|
||||
function check(rs: [], v: any) {
|
||||
for (let r of rs) {
|
||||
let res = r(v)
|
||||
if (res !== true) {
|
||||
return ['error', res]
|
||||
{
|
||||
required: true,
|
||||
validator(r: any, v: any) {
|
||||
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
|
||||
},
|
||||
trigger: ['input', 'blur']
|
||||
}
|
||||
}
|
||||
return ['', '']
|
||||
],
|
||||
password: [{
|
||||
required: true,
|
||||
validator(r: any, v: any) {
|
||||
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
let rules = ref({
|
||||
username: computed(() => {
|
||||
return check(ruleInline.username, data.value.username)
|
||||
})
|
||||
let uuid = computed(() => {
|
||||
return route.params.uuid || store.state.oauuid
|
||||
})
|
||||
|
||||
function login() {
|
||||
formRef.value.validate(e => {
|
||||
console.log(e)
|
||||
// @ts-ignore
|
||||
form_ref.value.validate((e:any) => {
|
||||
if (!e) {
|
||||
api.user.login(data.value.username, data.value.password, uuid.value as string).Start((url: string) => {
|
||||
msg.success('登录成功')
|
||||
store.dispatch('user/fetchUserData')
|
||||
let target = url
|
||||
if (route.query.redirect) {
|
||||
target = route.query.redirect as string
|
||||
}
|
||||
if (target && target.startsWith('http')) {
|
||||
window.location.href = target
|
||||
} else if (target) {
|
||||
router.push(target)
|
||||
} else {
|
||||
router.push({name: 'home'})
|
||||
}
|
||||
}, e => {
|
||||
console.log(e)
|
||||
msg.warning('登录失败:' + e)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (divs.value[0]) {
|
||||
// @ts-ignore
|
||||
divs.value[0].focus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
96
oaf/src/views/user_setting.vue
Normal file
96
oaf/src/views/user_setting.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="pt-10">
|
||||
<div class="flex justify-center">
|
||||
<div class="relative rounded-xl text-lg text-black" :style="{background: IsDark?'#555': '#d5d5d5'}">
|
||||
<div @click="ifInfo=true" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '#fc0005': ''}">
|
||||
个人信息
|
||||
</div>
|
||||
<div @click="ifInfo=false" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '': '#fc0005'}">
|
||||
账户管理
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-block flex justify-center mt-10">
|
||||
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight">
|
||||
<div v-if="ifInfo" class="animate__animated animate__faster">
|
||||
<n-form label-placement="left" label-width="80px" label-align="left">
|
||||
<n-form-item label="昵称">
|
||||
<n-input v-model:value="user.nickname" @blur="update('nickname')"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item label="头像">
|
||||
<n-upload
|
||||
action=""
|
||||
:headers="{'': ''}"
|
||||
:data="{}"
|
||||
>
|
||||
<n-avatar size="large" round :src="user.icon">
|
||||
</n-avatar>
|
||||
</n-upload>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
<div v-else class="animate__animated animate__faster">
|
||||
<n-form label-align="left" label-width="80px" label-placement="left">
|
||||
<n-form-item label="username">
|
||||
<n-input disabled v-model:value="user.username"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item label="phone">
|
||||
<n-input v-model:value="user.phone" @blur="update('phone')"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item label="email">
|
||||
<n-auto-complete :options="emailOptions" v-model:value="user.email"
|
||||
@blur="update('email')"></n-auto-complete>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed} from "vue";
|
||||
import {IsDark} from "../theme";
|
||||
import {useStore} from "../store";
|
||||
import api from "../api";
|
||||
import {useMessage} from "naive-ui";
|
||||
|
||||
let msg = useMessage()
|
||||
let store = useStore()
|
||||
|
||||
let ifInfo = ref(true)
|
||||
let user = ref({
|
||||
username: store.state.user.username,
|
||||
nickname: store.state.user.nickname,
|
||||
icon: store.state.user.icon,
|
||||
email: store.state.user.email,
|
||||
phone: store.state.user.phone,
|
||||
})
|
||||
let emailOptions = computed(() => {
|
||||
return ['@gmail.com', '@163.com', '@qq.com'].map((suffix) => {
|
||||
const prefix = user.value.email.split('@')[0]
|
||||
return {
|
||||
label: prefix + suffix,
|
||||
value: prefix + suffix
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function update(key: string) {
|
||||
// @ts-ignore
|
||||
let v = user.value[key]
|
||||
if (v === store.state.user[key]) {
|
||||
return
|
||||
}
|
||||
api.user.update(store.state.user.id, {[key]: v}).Start(e => {
|
||||
msg.success('更新成功')
|
||||
store.state.user[key] = v
|
||||
}, e => {
|
||||
msg.error('更新失败: ' + e.err)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
14
oaf/src/vuex.d.ts
vendored
Normal file
14
oaf/src/vuex.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import {ComponentCustomProperties} from 'vue'
|
||||
import {Store} from 'vuex'
|
||||
import {State as root} from './store'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
// 声明自己的 store state
|
||||
interface State extends root {
|
||||
}
|
||||
|
||||
// 为 `this.$store` 提供类型声明
|
||||
interface ComponentCustomProperties {
|
||||
$store: Store<State>
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,36 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import {defineConfig} from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
// host: '0.0.0.0',
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
proxy: {
|
||||
'/api': 'http://127.0.0.1:4001/'
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
// host: '0.0.0.0',
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:4001/',
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
},
|
||||
'/media': {
|
||||
target: 'http://127.0.0.1:4001/',
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: '../sub/static/',
|
||||
assetsDir: './',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// 重点在这里哦
|
||||
entryFileNames: `static/[name].[hash].js`,
|
||||
chunkFileNames: `static/[name].[hash].js`,
|
||||
assetFileNames: `static/[name].[hash].[ext]`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: '../build/static/',
|
||||
assetsDir: 'assets'
|
||||
}
|
||||
})
|
||||
|
||||
@ -216,10 +216,10 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@veypi/one-icon@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.0.tgz#15262e87644903d90a2124fbc9c95fbb6bb6bcb8"
|
||||
integrity sha512-zcUx3YzTIRiZe5XkJRZyKDgVzQThEWyZSCpQTreeYtKEObCl4B0Oa6CFYo4IToGD6uQmReM0R6oNPg5mhPjc2A==
|
||||
"@veypi/one-icon@2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.5.tgz#1083be30fc3bbb89aaf19501b00110d3ae9b1c77"
|
||||
integrity sha512-THnQh1zbH+glwDBLjP7rtcF3UtTQnzsxnUQEtUjAjv9Eo6rVSa1u4RsFA8CMDpoVcQiTMhqgRalbfjovdr11wg==
|
||||
dependencies:
|
||||
vue "^3.2.20"
|
||||
|
||||
@ -396,6 +396,11 @@ acorn@^7.0.0, acorn@^7.1.1:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||
|
||||
animate.css@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075"
|
||||
integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==
|
||||
|
||||
ansi-regex@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
|
||||
26
sub/init.go
26
sub/init.go
@ -5,7 +5,6 @@ import (
|
||||
"OneAuth/libs/auth"
|
||||
"OneAuth/models"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/veypi/utils/cmd"
|
||||
"github.com/veypi/utils/log"
|
||||
"strconv"
|
||||
)
|
||||
@ -20,6 +19,7 @@ func runInit(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// 初始化项目
|
||||
var appid uint
|
||||
|
||||
func InitSystem() error {
|
||||
db()
|
||||
@ -27,15 +27,9 @@ func InitSystem() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.CFG.APPID = self.ID
|
||||
cfg.CFG.APPKey = self.Key
|
||||
err = cmd.DumpCfg(cfg.Path, cfg.CFG)
|
||||
// TODO
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
appid = self.ID
|
||||
err = role(self.InitRoleID == 0)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func db() {
|
||||
@ -56,14 +50,14 @@ func selfApp() (*models.App, error) {
|
||||
self := &models.App{
|
||||
Name: "OA",
|
||||
Icon: "",
|
||||
UUID: "jU5Jo5hM",
|
||||
UUID: cfg.CFG.APPUUID,
|
||||
Des: "",
|
||||
Creator: 0,
|
||||
UserCount: 0,
|
||||
Hide: false,
|
||||
Host: "",
|
||||
UserRefreshUrl: "/",
|
||||
Key: "cB43wF94MLTksyBK",
|
||||
Key: cfg.CFG.APPKey,
|
||||
EnableRegister: true,
|
||||
EnableUserKey: true,
|
||||
EnableUser: true,
|
||||
@ -86,7 +80,7 @@ func role(reset_init_role bool) error {
|
||||
}
|
||||
var err error
|
||||
adminRole := &models.Role{
|
||||
AppID: cfg.CFG.APPID,
|
||||
AppID: appid,
|
||||
Name: "admin",
|
||||
IsUnique: false,
|
||||
}
|
||||
@ -96,7 +90,7 @@ func role(reset_init_role bool) error {
|
||||
}
|
||||
for _, na := range n {
|
||||
a := &models.Resource{
|
||||
AppID: cfg.CFG.APPID,
|
||||
AppID: appid,
|
||||
Name: na,
|
||||
Tag: "",
|
||||
Des: "",
|
||||
@ -112,7 +106,7 @@ func role(reset_init_role bool) error {
|
||||
}
|
||||
}
|
||||
userRole := &models.Role{
|
||||
AppID: cfg.CFG.APPID,
|
||||
AppID: appid,
|
||||
Name: "user",
|
||||
IsUnique: false,
|
||||
}
|
||||
@ -120,12 +114,12 @@ func role(reset_init_role bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, strconv.Itoa(int(cfg.CFG.APPID)))
|
||||
err = auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, strconv.Itoa(int(appid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reset_init_role {
|
||||
return cfg.DB().Model(&models.App{}).Where("id = ?", cfg.CFG.APPID).Update("init_role_id", adminRole.ID).Error
|
||||
return cfg.DB().Model(&models.App{}).Where("id = ?", appid).Update("init_role_id", adminRole.ID).Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1 +1,17 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>oaf</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"><link href="/css/chunk-6cfd0404.487f8d50.css" rel="prefetch"><link href="/js/about.a3055c06.js" rel="prefetch"><link href="/js/chunk-6cfd0404.65d5baaf.js" rel="prefetch"><link href="/app.b131f8d0f62acd99ab8e.js" rel="preload" as="script"><link href="/css/app.dafd5329.css" rel="preload" as="style"><link href="/css/chunk-vendors.dfe6062e.css" rel="preload" as="style"><link href="/js/chunk-vendors.83bac771.js" rel="preload" as="script"><link href="/css/chunk-vendors.dfe6062e.css" rel="stylesheet"><link href="/css/app.dafd5329.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but oaf doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.83bac771.js"></script><script src="/app.b131f8d0f62acd99ab8e.js"></script></body></html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/static/index.dddecd43.js"></script>
|
||||
<link rel="modulepreload" href="/static/vendor.ba3bd51d.js">
|
||||
<link rel="stylesheet" href="/static/vendor.3a295b6b.css">
|
||||
<link rel="stylesheet" href="/static/index.c49db26f.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
40
sub/web.go
40
sub/web.go
@ -6,16 +6,15 @@ import (
|
||||
"embed"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/core"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
"github.com/veypi/utils/log"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
//go:embed static
|
||||
//go:embed static/static
|
||||
var staticFiles embed.FS
|
||||
|
||||
//go:embed static/favicon.ico
|
||||
var icon []byte
|
||||
|
||||
//go:embed static/index.html
|
||||
var indexFile []byte
|
||||
|
||||
@ -37,34 +36,15 @@ func RunWeb(c *cli.Context) error {
|
||||
LoggerPath: cfg.CFG.LoggerPath,
|
||||
LoggerLevel: ll,
|
||||
})
|
||||
app.Router().EmbedFile("/", indexFile)
|
||||
app.Router().EmbedDir("/", staticFiles, "static/")
|
||||
|
||||
// TODO media 文件需要检验权限
|
||||
//app.Router().SubRouter("/media/").Static("/", cfg.CFG.EXEDir+"/media")
|
||||
|
||||
app.Router().SetNotFoundFunc(func(m core.Meta) {
|
||||
f, err := os.Open(cfg.CFG.EXEDir + "/static/index.html")
|
||||
if err != nil {
|
||||
m.WriteHeader(rfc.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
m.WriteHeader(rfc.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if info.IsDir() {
|
||||
// TODO:: dir list
|
||||
m.WriteHeader(rfc.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.ServeContent(m, m.Request(), info.Name(), info.ModTime(), f)
|
||||
})
|
||||
|
||||
api.Router(app.Router().SubRouter("api"))
|
||||
|
||||
// TODO media 文件需要检验权限
|
||||
app.Router().SubRouter("/media/").Static("/", cfg.CFG.MediaDir)
|
||||
app.Router().EmbedDir("/static", staticFiles, "static/static/")
|
||||
app.Router().EmbedFile("/favicon.ico", icon)
|
||||
app.Router().EmbedFile("/*", indexFile)
|
||||
|
||||
log.Info().Msg("\nRouting Table\n" + app.Router().String())
|
||||
return app.Run()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user