🎉: init project

This commit is contained in:
loveuer
2024-07-11 16:37:26 +08:00
commit c46458c6f2
159 changed files with 19246 additions and 0 deletions

87
internal/model/init.go Normal file
View File

@ -0,0 +1,87 @@
package model
import (
"fmt"
"github.com/loveuer/nf/nft/log"
"gorm.io/gorm"
"strings"
"ultone/internal/opt"
"ultone/internal/sqlType"
)
func Init(db *gorm.DB) error {
var err error
if err = initModel(db); err != nil {
return fmt.Errorf("model.MustInit: init models err=%v", err)
}
log.Info("MustInitModels: auto_migrate privilege model success")
if err = initData(db); err != nil {
return fmt.Errorf("model.MustInit: init datas err=%v", err)
}
return nil
}
func initModel(client *gorm.DB) error {
if err := client.AutoMigrate(
&User{},
&OpLog{},
); err != nil {
return err
}
log.Info("InitModels: auto_migrate user model success")
return nil
}
func initData(client *gorm.DB) error {
var (
err error
)
{
count := 0
if err = client.Model(&User{}).Select("count(id)").Take(&count).Error; err != nil {
return err
}
if count < len(initUsers) {
log.Warn("mustInitDatas: user count = 0, start init...")
for _, user := range initUsers {
if err = client.Model(&User{}).Create(user).Error; err != nil {
if !strings.Contains(err.Error(), "SQLSTATE 23505") {
return err
}
}
}
if opt.Cfg.DB.Type == "postgresql" {
if err = client.Exec(`SELECT setval('users_id_seq', (SELECT MAX(id) FROM users))`).Error; err != nil {
return err
}
}
log.Info("InitDatas: creat init users success")
} else {
ps := make(sqlType.NumSlice[Privilege], 0)
for _, item := range Privilege(0).All() {
ps = append(ps, item.(Privilege))
}
if err = client.Model(&User{}).Where("id = ?", initUsers[0].Id).
Updates(map[string]any{
"privileges": ps,
}).Error; err != nil {
return err
}
log.Info("initDatas: update init users success")
}
}
return nil
}

View File

@ -0,0 +1,17 @@
package model
type Enum interface {
Value() int64
Code() string
Label() string
MarshalJSON() ([]byte, error)
All() []Enum
}
type OpLogger interface {
Enum
Render(content map[string]any) (string, error)
Template() string
}

294
internal/model/oplog.go Normal file
View File

@ -0,0 +1,294 @@
package model
import (
"bytes"
"encoding/base64"
"encoding/json"
"github.com/spf13/cast"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/minify/v2/html"
"html/template"
"time"
"ultone/internal/sqlType"
)
var (
FuncMap = template.FuncMap{
"time_format": func(mil any, format string) string {
return time.UnixMilli(cast.ToInt64(mil)).Format(format)
},
}
)
var (
_ OpLogger = (*OpLogType)(nil)
)
type OpLogType uint64
const (
OpLogTypeLogin OpLogType = iota + 1
OpLogTypeLogout
OpLogTypeCreateUser
OpLogTypeUpdateUser
OpLogTypeDeleteUser
// todo: 添加自己的操作日志 分类
)
func (o OpLogType) Value() int64 {
return int64(o)
}
func (o OpLogType) Code() string {
switch o {
case OpLogTypeLogin:
return "login"
case OpLogTypeLogout:
return "logout"
case OpLogTypeCreateUser:
return "create_user"
case OpLogTypeUpdateUser:
return "update_user"
case OpLogTypeDeleteUser:
return "delete_user"
default:
return "unknown"
}
}
func (o OpLogType) Label() string {
switch o {
case OpLogTypeLogin:
return "登入"
case OpLogTypeLogout:
return "登出"
case OpLogTypeCreateUser:
return "创建用户"
case OpLogTypeUpdateUser:
return "修改用户"
case OpLogTypeDeleteUser:
return "删除用户"
default:
return "未知"
}
}
func (o OpLogType) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"value": o.Value(),
"code": o.Code(),
"label": o.Label(),
})
}
func (o OpLogType) All() []Enum {
return []Enum{
OpLogTypeLogin,
OpLogTypeLogout,
OpLogTypeCreateUser,
OpLogTypeUpdateUser,
OpLogTypeDeleteUser,
}
}
func _trimHTML(v []byte) string {
return base64.StdEncoding.EncodeToString(v)
}
var (
_mini = minify.New()
)
func init() {
_mini.AddFunc("text/html", html.Minify)
}
func (o OpLogType) Render(content map[string]any) (string, error) {
var (
err error
render *template.Template
buf bytes.Buffer
bs []byte
)
if render, err = template.New(o.Code()).
Funcs(FuncMap).
Parse(o.Template()); err != nil {
return "", err
}
if err = render.Execute(&buf, content); err != nil {
return "", err
}
if bs, err = _mini.Bytes("text/html", buf.Bytes()); err != nil {
return "", err
}
return _trimHTML(bs), nil
}
const (
oplogTemplateLogin = `
<div class="nf-op-log">
用户
<span
class="nf-op-log-user nf-op-log-keyword"
nf-op-log-user="{{ .user_id }}"
>{{ .username }}
</span>
<span
class="nf-op-log-time nf-op-log-keyword"
nf-op-log-time="{{ .time }}"
>{{ time_format .time "2006-01-02 15:04:05" }}
</span>
<span
class="nf-op-log-ip nf-op-log-keyword"
>{{ .ip }}
</span>
<span
class="nf-op-log-op nf-op-log-keyword"
>
登入
</span>
了系统
</div>
`
oplogTemplateLogout = `
<div class="nf-op-log">
用户
<span
class="nf-op-log-user nf-op-log-keyword"
nf-op-log-user="{{ .user_id }}"
>{{ .username }}
</span>
<span
class="nf-op-log-time nf-op-log-keyword"
nf-op-log-time="{{ .time }}"
>{{ time_format .time "2006-01-02 15:04:05" }}
</span>
<span
class="nf-op-log-ip nf-op-log-keyword"
>{{ .ip }}
</span>
<span
class="nf-op-log-op nf-op-log-keyword"
>
登出
</span>
了系统
</div>
`
oplogTemplateCreateUser = `
<div class="nf-op-log">
用户
<span
class="nf-op-log-user nf-op-log-keyword"
nf-op-log-user="{{ .user_id }}"
>{{ .username }}
</span>
<span
class="nf-op-log-time nf-op-log-keyword"
nf-op-log-time="{{ .time }}"
>{{ time_format .time "2006-01-02 15:04:05" }}
</span>
<span class="nf-op-log-keyword">
创建
</span>
了用户
<span
class="nf-op-log-target nf-op-log-keyword"
nf-op-log-target="{{ .target_id }}"
>{{ .target_username }}
</span>
</div>
`
oplogTemplateUpdateUser = `
<div class="nf-op-log">
用户
<span
class="nf-op-log-user nf-op-log-keyword"
nf-op-log-user='{{ .user_id }}'
>{{ .username }}
</span>
<span
class="nf-op-log-time nf-op-log-keyword"
nf-op-log-time='{{ .time }}'
>{{ time_format .time "2006-01-02 15:04:05" }}
</span>
<span class="nf-op-log-keyword">
编辑
</span>
了用户
<span
class="nf-op-log-target nf-op-log-keyword"
nf-op-log-target="{{ .target_id }}"
>{{ .target_username }}
</span>
</div>
`
oplogTemplateDeleteUser = `
<div class="nf-op-log">
用户
<span
class="nf-op-log-user nf-op-log-keyword"
nf-op-log-user="{{ .user_id }}"
>{{ .username }}
</span>
<span
class="nf-op-log-time nf-op-log-keyword"
nf-op-log-time="{{ .time }}"
>{{ time_format .time "2006-01-02 15:04:05" }}
</span>
<span class="nf-op-log-keyword">
删除
</span>
了用户
<span
class="nf-op-log-target nf-op-log-keyword"
nf-op-log-target="{{ .target_id }}"
>{{ .target_username }}
</span>
</div>
`
)
func (o OpLogType) Template() string {
switch o {
case OpLogTypeLogin:
return oplogTemplateLogin
case OpLogTypeLogout:
return oplogTemplateLogout
case OpLogTypeCreateUser:
return oplogTemplateCreateUser
case OpLogTypeUpdateUser:
return oplogTemplateUpdateUser
case OpLogTypeDeleteUser:
return oplogTemplateDeleteUser
default:
return `<div>错误的日志类型</div>`
}
}
type OpLog struct {
Id uint64 `json:"id" gorm:"primaryKey;column:id"`
CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"`
UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"`
DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"`
UserId uint64 `json:"user_id" gorm:"column:user_id"`
Username string `json:"username" gorm:"column:username;varchar(128)"`
Type OpLogType `json:"type" gorm:"column:type;type:varchar(128)"`
Content sqlType.JSONB `json:"content" gorm:"column:content;type:jsonb"`
HTML string `json:"html" gorm:"-"`
}

View File

@ -0,0 +1,61 @@
package model
import "encoding/json"
type Privilege uint64
type _privilege struct {
Value int64 `json:"value"`
Code string `json:"code"`
Label string `json:"label"`
}
const (
PrivilegeUserManage Privilege = iota + 1
PrivilegeOpLog
)
func (p Privilege) Value() int64 {
return int64(p)
}
func (p Privilege) Code() string {
switch p {
case PrivilegeUserManage:
return "user_manage"
case PrivilegeOpLog:
return "oplog"
default:
return "unknown"
}
}
func (p Privilege) Label() string {
switch p {
case PrivilegeUserManage:
return "用户管理"
case PrivilegeOpLog:
return "操作日志"
default:
return "未知"
}
}
func (p Privilege) MarshalJSON() ([]byte, error) {
_p := &_privilege{
Value: int64(p),
Code: p.Code(),
Label: p.Label(),
}
return json.Marshal(_p)
}
func (p Privilege) All() []Enum {
return []Enum{
PrivilegeUserManage,
PrivilegeOpLog,
}
}
var _ Enum = (*Privilege)(nil)

85
internal/model/role.go Normal file
View File

@ -0,0 +1,85 @@
package model
import (
"encoding/json"
"gorm.io/gorm"
"ultone/internal/opt"
)
type _role struct {
Value uint8 `json:"value"`
Code string `json:"code"`
Label string `json:"label"`
}
type Role uint8
var _ Enum = Role(0)
func (u Role) MarshalJSON() ([]byte, error) {
m := _role{
Value: uint8(u),
Code: u.Code(),
Label: u.Label(),
}
return json.Marshal(m)
}
const (
RoleRoot Role = 255
RoleAdmin Role = 254
RoleUser Role = 100
)
func (u Role) Code() string {
switch u {
case RoleRoot:
return "root"
case RoleAdmin:
return "admin"
case RoleUser:
return "user"
default:
return "unknown"
}
}
func (u Role) Label() string {
switch u {
case RoleRoot:
return "根用户"
case RoleAdmin:
return "管理员"
case RoleUser:
return "用户"
default:
return "未知"
}
}
func (u Role) Value() int64 {
return int64(u)
}
func (u Role) All() []Enum {
return []Enum{
RoleAdmin,
RoleUser,
}
}
func (u Role) Where(db *gorm.DB) *gorm.DB {
if opt.RoleMustLess {
return db.Where("users.role < ?", u.Value())
} else {
return db.Where("users.role <= ?", u.Value())
}
}
func (u Role) CanOP(op *User) bool {
if opt.RoleMustLess {
return op.Role > u
}
return op.Role >= u
}

226
internal/model/user.go Normal file
View File

@ -0,0 +1,226 @@
package model
import (
"encoding/json"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/loveuer/nf/nft/log"
"github.com/samber/lo"
"github.com/spf13/cast"
"strings"
"time"
"ultone/internal/opt"
"ultone/internal/sqlType"
"ultone/internal/tool"
)
var (
initUsers = []*User{
{
Id: 1,
Username: "admin",
Password: tool.NewPassword("123456"),
Nickname: "admin",
Role: RoleAdmin,
Privileges: lo.Map(Privilege(0).All(), func(item Enum, index int) Privilege {
return item.(Privilege)
}),
CreatedById: 1,
CreatedByName: "admin",
ActiveAt: time.Now().UnixMilli(),
Deadline: time.Now().AddDate(100, 0, 0).UnixMilli(),
},
}
_ Enum = Status(0)
)
type Status uint64
const (
StatusNormal Status = iota
StatusFrozen
)
func (s Status) Value() int64 {
return int64(s)
}
func (s Status) Code() string {
switch s {
case StatusNormal:
return "normal"
case StatusFrozen:
return "frozen"
default:
return "unknown"
}
}
func (s Status) Label() string {
switch s {
case StatusNormal:
return "正常"
case StatusFrozen:
return "冻结"
default:
return "异常"
}
}
func (s Status) All() []Enum {
return []Enum{
StatusNormal,
StatusFrozen,
}
}
func (s Status) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"value": s.Value(),
"code": s.Code(),
"label": s.Label(),
})
}
type User struct {
Id uint64 `json:"id" gorm:"primaryKey;column:id"`
CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"`
UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"`
DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"`
Username string `json:"username" gorm:"column:username;type:varchar(64);unique"`
Password string `json:"-" gorm:"column:password;type:varchar(256)"`
Status Status `json:"status" gorm:"column:status;default:0"`
Nickname string `json:"nickname" gorm:"column:nickname;type:varchar(64)"`
Comment string `json:"comment" gorm:"column:comment"`
Role Role `json:"role" gorm:"column:role"`
Privileges sqlType.NumSlice[Privilege] `json:"privileges" gorm:"column:privileges;type:bigint[]"`
CreatedById uint64 `json:"created_by_id" gorm:"column:created_by_id"`
CreatedByName string `json:"created_by_name" gorm:"column:created_by_name;type:varchar(64)"`
ActiveAt int64 `json:"active_at" gorm:"column:active_at"`
Deadline int64 `json:"deadline" gorm:"column:deadline"`
LoginAt int64 `json:"login_at" gorm:"-"`
}
func (u *User) CheckStatus(mustOk bool) error {
switch u.Status {
case StatusNormal:
case StatusFrozen:
if mustOk {
return errors.New("用户被冻结")
}
default:
return errors.New("用户状态未知")
}
return nil
}
func (u *User) IsValid(mustOk bool) error {
now := time.Now()
if now.UnixMilli() >= u.Deadline {
return errors.New("用户已过期")
}
if now.UnixMilli() < u.ActiveAt {
return errors.New("用户未启用")
}
if u.DeletedAt > 0 {
return errors.New("用户不存在")
}
return u.CheckStatus(mustOk)
}
func (u *User) JwtEncode() (token string, err error) {
now := time.Now()
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
"id": u.Id,
"username": u.Username,
"status": u.Status,
"deadline": u.Deadline,
"login_at": now.UnixMilli(),
})
if token, err = jwtToken.SignedString([]byte(opt.JwtTokenSecret)); err != nil {
err = fmt.Errorf("JwtEncode: jwt token signed secret err: %v", err)
log.Error(err.Error())
return "", nil
}
return
}
func (u *User) FromJwt(token string) *User {
var (
ok bool
err error
pt *jwt.Token
claims jwt.MapClaims
)
token = strings.TrimPrefix(token, "Bearer ")
if pt, err = jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
if _, ok = t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
}
return []byte(opt.JwtTokenSecret), nil
}); err != nil {
log.Error("jwt parse err: %v", err)
return nil
}
if !pt.Valid {
log.Warn("parsed jwt invalid")
return nil
}
if claims, ok = pt.Claims.(jwt.MapClaims); !ok {
log.Error("convert jwt claims err")
return nil
}
u.Id = cast.ToUint64(claims["user_id"])
u.Username = cast.ToString(claims["username"])
u.Status = Status(cast.ToInt64(claims["status"]))
u.Deadline = cast.ToInt64(claims["deadline"])
u.LoginAt = cast.ToInt64(claims["login_at"])
return u
}
func (u User) MarshalBinary() ([]byte, error) {
return json.Marshal(map[string]any{
"id": u.Id,
"created_at": u.CreatedAt,
"updated_at": u.UpdatedAt,
"deleted_at": u.DeletedAt,
"username": u.Username,
"status": u.Status.Value(),
"nickname": u.Nickname,
"comment": u.Comment,
"role": uint8(u.Role),
"privileges": lo.Map(u.Privileges, func(item Privilege, index int) int64 {
return item.Value()
}),
"created_by_id": u.CreatedById,
"created_by_name": u.CreatedByName,
"active_at": u.ActiveAt,
"deadline": u.Deadline,
"login_at": u.LoginAt,
})
}