This commit is contained in:
2025-03-28 17:51:34 +08:00
parent da612380e0
commit 5c8802d2f0
68 changed files with 3422 additions and 630 deletions

View File

@@ -25,6 +25,40 @@ type Config struct {
Smb Smb `mapstructure:"smb" json:"smb" yaml:"smb"`
}
func New(path string) (*Config, error) {
fp := "."
fn := ConfigDefaultFile
if len(path) > 0 {
fp, fn = filepath.Split(path)
if len(fp) == 0 {
fp = "."
}
}
v := viper.New()
v.AddConfigPath(fp)
v.SetConfigName(fn)
v.SetConfigType("yaml")
if err := v.ReadInConfig(); err != nil {
return nil, err
}
v.WatchConfig()
var conf *Config
v.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("config file changed:", e.Name)
if err := v.Unmarshal(&conf); err != nil {
fmt.Println(err)
}
})
if err := v.Unmarshal(&conf); err != nil {
return nil, err
}
return conf, nil
}
func Init(path string) error {
fp := "."
fn := ConfigDefaultFile

View File

@@ -5,6 +5,7 @@ type SearchDto struct {
SearchTimeEnd string `json:"searchTimeEnd"`
SearchStatus int `json:"searchStatus"`
SearchName string `json:"searchName"`
SearchID int64 `json:"searchID"`
SearchKey string `json:"searchKey"`
SearchParentID int `json:"searchParentId"`
SearchDepartmentID int `json:"searchDepartmentId"`

View File

@@ -12,6 +12,7 @@ type DTreeDto struct {
Title string `json:"title"`
Last bool `json:"last"`
ParentId string `json:"parentId"`
Spread bool `json:"spread"`
Children []*DTreeDto `json:"children"`
}

View File

@@ -19,3 +19,9 @@ type XmSelectStrDto struct {
Name string `json:"name"`
Value string `json:"value"`
}
type XmSelectTreeDto struct {
Name string `json:"name"`
Value string `json:"value"`
Children []*XmSelectTreeDto `json:"children"`
}

View File

@@ -14,13 +14,13 @@ import (
// ****************** conn ******************
func newDsn() string {
func newDsn(conf config.DB) string {
return fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
config.File.DB.Username,
config.File.DB.Password,
config.File.DB.Host,
config.File.DB.Port,
config.File.DB.DBName)
conf.Username,
conf.Password,
conf.Host,
conf.Port,
conf.DBName)
}
// ****************** errors ******************
@@ -69,9 +69,26 @@ type SQLStore struct {
*Queries
}
func NewIStore(ctx context.Context, conf config.DB) (Store, error) {
pool, err := pgxpool.New(ctx, newDsn(conf))
if err != nil {
return nil, err
}
err = pool.Ping(ctx)
if err != nil {
return nil, err
}
return &SQLStore{
connPool: pool,
Queries: New(pool),
}, nil
}
// NewStore creates a new store
func NewStore(ctx context.Context) error {
pool, err := pgxpool.New(ctx, newDsn())
pool, err := pgxpool.New(ctx, newDsn(config.File.DB))
if err != nil {
return err
}

View File

@@ -0,0 +1,46 @@
package biz
import (
db "management/internal/db/sqlc"
commonv1 "management/internal/erpserver/biz/v1/common"
systemv1 "management/internal/erpserver/biz/v1/system"
"management/internal/pkg/redis"
"management/internal/pkg/session"
)
// IBiz 定义了业务层需要实现的方法.
type IBiz interface {
// 获取公共业务接口.
CommonV1() commonv1.CommonBiz
// 获取系统业务接口.
SystemV1() systemv1.SystemBiz
}
// biz 是 IBiz 的一个具体实现.
type biz struct {
store db.Store
redis redis.IRedis
session session.ISession
}
// 确保 biz 实现了 IBiz 接口.
var _ IBiz = (*biz)(nil)
// NewBiz 创建一个 IBiz 类型的实例.
func NewBiz(store db.Store, redis redis.IRedis, session session.ISession) *biz {
return &biz{
store: store,
redis: redis,
session: session,
}
}
// CommonV1 返回一个实现了 CommonBiz 接口的实例.
func (b *biz) CommonV1() commonv1.CommonBiz {
return commonv1.New()
}
// SystemV1 返回一个实现了 SystemBiz 接口的实例.
func (b *biz) SystemV1() systemv1.SystemBiz {
return systemv1.New(b.store, b.redis, b.session)
}

View File

@@ -0,0 +1,36 @@
package common
import (
"github.com/mojocn/base64Captcha"
)
// CaptchaBiz 定义处理验证码请求所需的方法.
type CaptchaBiz interface {
Generate(height int, width int, length int, maxSkew float64, dotCount int) (id, b64s, answer string, err error)
Verify(id, answer string, clear bool) bool
}
// captchaBiz 是 CaptchaBiz 接口的实现.
type captchaBiz struct{}
// 确保 captchaBiz 实现了 CaptchaBiz 接口.
var _ CaptchaBiz = (*captchaBiz)(nil)
func NewCaptcha() *captchaBiz {
return &captchaBiz{}
}
var captchaStore base64Captcha.Store = base64Captcha.DefaultMemStore
func (b *captchaBiz) Generate(height int, width int, length int, maxSkew float64, dotCount int) (id, b64s, answer string, err error) {
driver := base64Captcha.NewDriverDigit(height, width, length, maxSkew, dotCount)
// driver := base64Captcha.NewDriverString(config.File.Captcha.ImgHeight,
// config.File.Captcha.ImgWidth,
// 6, 1, keyLong, source, nil, nil, nil)
cp := base64Captcha.NewCaptcha(driver, captchaStore)
return cp.Generate()
}
func (b *captchaBiz) Verify(id, answer string, clear bool) bool {
return captchaStore.Verify(id, answer, clear)
}

View File

@@ -0,0 +1,17 @@
package common
type CommonBiz interface {
CaptchaBiz() CaptchaBiz
}
type commonBiz struct{}
var _ CommonBiz = (*commonBiz)(nil)
func New() *commonBiz {
return &commonBiz{}
}
func (b *commonBiz) CaptchaBiz() CaptchaBiz {
return NewCaptcha()
}

View File

@@ -0,0 +1,31 @@
package system
import (
"context"
db "management/internal/db/sqlc"
)
type AuditBiz interface {
Create(ctx context.Context, arg *db.CreateSysAuditLogParams) error
AuditExpansion
}
type AuditExpansion interface{}
type auditBiz struct {
store db.Store
}
var _ AuditBiz = (*auditBiz)(nil)
func NewAudit(store db.Store) *auditBiz {
return &auditBiz{
store: store,
}
}
func (b *auditBiz) Create(ctx context.Context, arg *db.CreateSysAuditLogParams) error {
return b.store.CreateSysAuditLog(ctx, arg)
}

View File

@@ -0,0 +1,63 @@
package system
import (
"context"
"encoding/json"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/global/keys"
"management/internal/global/pearadmin"
"management/internal/pkg/redis"
"management/internal/pkg/session"
)
type ConfigBiz interface {
ConfigExpansion
}
type ConfigExpansion interface {
Pear(ctx context.Context) (*dto.PearConfig, error)
}
type configBiz struct {
store db.Store
redis redis.IRedis
session session.ISession
}
var _ ConfigBiz = (*configBiz)(nil)
func NewConfig(store db.Store, redis redis.IRedis, session session.ISession) *configBiz {
return &configBiz{
store: store,
redis: redis,
session: session,
}
}
func (b *configBiz) Pear(ctx context.Context) (*dto.PearConfig, error) {
// 判断redis是否存储
key := keys.GetManageKey(ctx, keys.PearAdmin)
bs, err := b.redis.GetBytes(ctx, key)
if err == nil {
var res *dto.PearConfig
if err := json.Unmarshal(bs, &res); err == nil {
return res, nil
}
}
conf, err := b.store.GetSysConfigByKey(ctx, pearadmin.PearKey)
if err != nil {
return nil, err
}
var pear dto.PearConfig
if err := json.Unmarshal(conf.Value, &pear); err != nil {
return nil, err
}
_ = b.redis.Set(ctx, key, conf.Value, time.Hour*6)
return &pear, nil
}

View File

@@ -0,0 +1,183 @@
package system
import (
"context"
"encoding/json"
"strconv"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/erpserver/model/view"
"management/internal/global/keys"
"management/internal/pkg/redis"
"management/internal/pkg/session"
)
type DepartmentBiz interface {
Create(ctx context.Context, arg *db.CreateSysDepartmentParams) (*db.SysDepartment, error)
Update(ctx context.Context, arg *db.UpdateSysDepartmentParams) (*db.SysDepartment, error)
All(ctx context.Context) ([]*db.SysDepartment, error)
List(ctx context.Context, q dto.SearchDto) ([]*db.SysDepartment, int64, error)
Get(ctx context.Context, id int32) (*db.SysDepartment, error)
Refresh(ctx context.Context) ([]*db.SysDepartment, error)
RebuildParentPath(ctx context.Context) error
Tree(ctx context.Context, id int32) ([]*view.LayuiTree, error)
XmSelect(ctx context.Context, id int32) ([]*view.XmSelectTree, error)
DepartmentExpansion
}
type DepartmentExpansion interface{}
type departmentBiz struct {
store db.Store
redis redis.IRedis
session session.ISession
}
var _ DepartmentBiz = (*departmentBiz)(nil)
func NewDepartment(store db.Store, redis redis.IRedis, session session.ISession) *departmentBiz {
return &departmentBiz{
store: store,
redis: redis,
session: session,
}
}
func (b *departmentBiz) All(ctx context.Context) ([]*db.SysDepartment, error) {
key := keys.GetManageKey(ctx, keys.AllDepartments)
bs, err := redis.GetBytes(ctx, key)
if err == nil {
var res []*db.SysDepartment
if err := json.Unmarshal(bs, &res); err == nil {
return res, nil
}
}
return b.Refresh(ctx)
}
func (b *departmentBiz) List(ctx context.Context, q dto.SearchDto) ([]*db.SysDepartment, int64, error) {
countArg := &db.CountSysDepartmentConditionParams{
IsStatus: q.SearchStatus != 9999,
Status: int32(q.SearchStatus),
IsID: q.SearchID != 0,
ID: int32(q.SearchID),
IsParentID: q.SearchParentID != 0,
ParentID: int32(q.SearchParentID),
Name: q.SearchName,
}
dataArg := &db.ListSysDepartmentConditionParams{
IsStatus: q.SearchStatus != 9999,
Status: int32(q.SearchStatus),
IsID: q.SearchID != 0,
ID: int32(q.SearchID),
IsParentID: q.SearchParentID != 0,
ParentID: int32(q.SearchParentID),
Name: q.SearchName,
Skip: (int32(q.Page) - 1) * int32(q.Rows),
Size: int32(q.Rows),
}
count, err := b.store.CountSysDepartmentCondition(ctx, countArg)
if err != nil {
return nil, 0, err
}
departs, err := b.store.ListSysDepartmentCondition(ctx, dataArg)
if err != nil {
return nil, 0, err
}
return departs, count, nil
}
func (b *departmentBiz) Get(ctx context.Context, id int32) (*db.SysDepartment, error) {
return b.store.GetSysDepartment(ctx, id)
}
func (b *departmentBiz) Create(ctx context.Context, arg *db.CreateSysDepartmentParams) (*db.SysDepartment, error) {
return b.store.CreateSysDepartment(ctx, arg)
}
func (b *departmentBiz) Update(ctx context.Context, arg *db.UpdateSysDepartmentParams) (*db.SysDepartment, error) {
return b.store.UpdateSysDepartment(ctx, arg)
}
func (b *departmentBiz) Refresh(ctx context.Context) ([]*db.SysDepartment, error) {
all, err := b.store.AllSysDepartment(ctx)
if err != nil {
return nil, err
}
bs, err := json.Marshal(all)
if err != nil {
return nil, err
}
key := keys.GetManageKey(ctx, keys.AllDepartments)
err = redis.Set(ctx, key, bs, time.Hour*6)
if err != nil {
return nil, err
}
return all, nil
}
func (h *departmentBiz) RebuildParentPath(ctx context.Context) error {
return h.store.SysDepartmentRebuildPath(ctx)
}
func (h *departmentBiz) Tree(ctx context.Context, id int32) ([]*view.LayuiTree, error) {
all, err := h.All(ctx)
if err != nil {
return nil, err
}
return toLayuiTree(id, all), nil
}
func (h *departmentBiz) XmSelect(ctx context.Context, id int32) ([]*view.XmSelectTree, error) {
all, err := h.All(ctx)
if err != nil {
return nil, err
}
return toXmSelectTree(id, all), nil
}
func toXmSelectTree(parentId int32, data []*db.SysDepartment) []*view.XmSelectTree {
var res []*view.XmSelectTree
for _, v := range data {
if v.ParentID == parentId {
item := view.XmSelectTree{
Name: v.Name,
Value: strconv.FormatInt(int64(v.ID), 10),
Children: toXmSelectTree(v.ID, data),
}
res = append(res, &item)
}
}
return res
}
func toLayuiTree(parentId int32, data []*db.SysDepartment) []*view.LayuiTree {
var res []*view.LayuiTree
for _, v := range data {
if v.ParentID == parentId {
item := view.LayuiTree{}
item.ID = strconv.FormatInt(int64(v.ID), 10)
item.Title = v.Name
item.Children = toLayuiTree(v.ID, data)
if v.ParentID == 0 {
item.Spread = true
}
res = append(res, &item)
}
}
return res
}

View File

@@ -0,0 +1,311 @@
package system
import (
"context"
"encoding/json"
"strconv"
"strings"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/global/keys"
"management/internal/pkg/redis"
"management/internal/pkg/session"
)
type MenuBiz interface {
MenuExpansion
}
type MenuExpansion interface {
GetSysMenuByUrl(ctx context.Context, url string) (*db.SysMenu, error)
ListOwnerMenuByRoleID(ctx context.Context, roleID int32) ([]*dto.OwnerMenuDto, error)
RecursiveSysMenus(ctx context.Context, roleID int32) ([]*dto.MenuUIDto, error)
SetRecursiveSysMenus(ctx context.Context, roleID int32) ([]*dto.MenuUIDto, error)
MapOwnerMenuByRoleID(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error)
SetOwnerMapMenuByRoleID(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error)
}
type menuBiz struct {
store db.Store
redis redis.IRedis
session session.ISession
}
var _ MenuBiz = (*menuBiz)(nil)
func NewMenu(store db.Store, redis redis.IRedis, session session.ISession) *menuBiz {
return &menuBiz{
store: store,
redis: redis,
session: session,
}
}
func (b *menuBiz) GetSysMenuByUrl(ctx context.Context, url string) (*db.SysMenu, error) {
return b.store.GetSysMenuByUrl(ctx, url)
}
func (b *menuBiz) ListOwnerMenuByRoleID(ctx context.Context, roleID int32) ([]*dto.OwnerMenuDto, error) {
// 判断redis是否存储
key := keys.GetManageKey(ctx, keys.OwnerMenus, roleID)
bs, err := b.redis.GetBytes(ctx, key)
if err == nil {
var res []*dto.OwnerMenuDto
if err := json.Unmarshal(bs, &res); err == nil {
return res, nil
}
}
return b.SetOwnerListMenuByRoleID(ctx, roleID)
}
func (b *menuBiz) SetOwnerListMenuByRoleID(ctx context.Context, roleID int32) ([]*dto.OwnerMenuDto, error) {
menus, err := b.ownerMenusByRoleID(ctx, roleID)
if err != nil {
return nil, err
}
var res []*dto.OwnerMenuDto
for _, menu := range menus {
res = append(res, &dto.OwnerMenuDto{
ID: menu.ID,
DisplayName: menu.DisplayName,
Url: menu.Url,
ParentID: menu.ParentID,
Avatar: menu.Avatar,
Style: menu.Style,
IsList: menu.IsList,
})
}
bs, err := json.Marshal(res)
if err != nil {
return nil, err
}
key := keys.GetManageKey(ctx, keys.OwnerMenus, roleID)
_ = redis.Set(ctx, key, bs, time.Hour*6)
return res, nil
}
func (b *menuBiz) RecursiveSysMenus(ctx context.Context, roleID int32) ([]*dto.MenuUIDto, error) {
// 判断redis是否存储
key := keys.GetManageKey(ctx, keys.RecursiveMenus, roleID)
bs, err := b.redis.GetBytes(ctx, key)
if err == nil {
var res []*dto.MenuUIDto
if err := json.Unmarshal(bs, &res); err == nil {
return res, nil
}
}
return b.SetRecursiveSysMenus(ctx, roleID)
}
func (b *menuBiz) SetRecursiveSysMenus(ctx context.Context, roleID int32) ([]*dto.MenuUIDto, error) {
// 判断当前用户是否有vip角色
role, err := b.store.GetSysRole(ctx, roleID)
if err != nil {
return nil, err
}
var menus []*db.SysMenu
if role.Vip {
// vip 用户
all, err := b.store.RecursiveSysMenus(ctx)
if err != nil {
return nil, err
}
menus = convertToMenuUIDto(all)
} else {
// not vip
all, err := b.store.RecursiveSysMenusByRoleID(ctx, roleID)
if err != nil {
return nil, err
}
menus = convertToMenuUIDto2(all)
}
menuList := uniqueSysMenus(menus)
if len(menuList) == 0 {
return nil, nil
}
tree := convertToUITree(menuList, 0)
bs, err := json.Marshal(tree)
if err != nil {
return nil, err
}
key := keys.GetManageKey(ctx, keys.RecursiveMenus, roleID)
_ = redis.Set(ctx, key, bs, time.Hour*6)
return tree, nil
}
func (b *menuBiz) MapOwnerMenuByRoleID(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
// 判断redis是否存储
key := keys.GetManageKey(ctx, keys.OwnerMenus, roleID)
bs, err := b.redis.GetBytes(ctx, key)
if err == nil {
var res map[string]*dto.OwnerMenuDto
if err := json.Unmarshal(bs, &res); err == nil {
return res, nil
}
}
return b.SetOwnerMapMenuByRoleID(ctx, roleID)
}
func (b *menuBiz) SetOwnerMapMenuByRoleID(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
result := make(map[string]*dto.OwnerMenuDto)
menus, err := b.ownerMenusByRoleID(ctx, roleID)
if err != nil {
return result, err
}
for _, menu := range menus {
result[menu.Url] = &dto.OwnerMenuDto{
ID: menu.ID,
DisplayName: menu.DisplayName,
Url: menu.Url,
ParentID: menu.ParentID,
Avatar: menu.Avatar,
Style: menu.Style,
IsList: menu.IsList,
}
}
bs, err := json.Marshal(result)
if err != nil {
return nil, err
}
key := keys.GetManageKey(ctx, keys.OwnerMenus, roleID)
_ = redis.Set(ctx, key, bs, time.Hour*6)
return result, nil
}
func (b *menuBiz) ownerMenusByRoleID(ctx context.Context, roleID int32) ([]*db.SysMenu, error) {
// 判断当前用户是否有vip角色
role, err := b.store.GetSysRole(ctx, roleID)
if err != nil {
return nil, err
}
var e error
var menus []*db.SysMenu
if role.Vip {
// vip 用户
menus, e = b.store.AllSysMenu(ctx)
if e != nil {
return nil, err
}
} else {
// not vip
menus, e = b.store.ListSysMenuByRoleID(ctx, roleID)
if e != nil {
return nil, err
}
}
return menus, nil
}
func convertToMenuUIDto(data []*db.RecursiveSysMenusRow) []*db.SysMenu {
var res []*db.SysMenu
for _, item := range data {
temp := &db.SysMenu{
ID: item.ID,
Name: item.Name,
DisplayName: item.DisplayName,
Url: item.Url,
Type: item.Type,
ParentID: item.ParentID,
ParentPath: item.ParentPath,
Avatar: item.Avatar,
Style: item.Style,
Visible: item.Visible,
IsList: item.IsList,
Status: item.Status,
Sort: item.Sort,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
}
res = append(res, temp)
}
return res
}
func convertToMenuUIDto2(data []*db.RecursiveSysMenusByRoleIDRow) []*db.SysMenu {
var res []*db.SysMenu
for _, item := range data {
temp := &db.SysMenu{
ID: item.ID,
Name: item.Name,
DisplayName: item.DisplayName,
Url: item.Url,
Type: item.Type,
ParentID: item.ParentID,
ParentPath: item.ParentPath,
Avatar: item.Avatar,
Style: item.Style,
Visible: item.Visible,
IsList: item.IsList,
Status: item.Status,
Sort: item.Sort,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
}
res = append(res, temp)
}
return res
}
func convertToUITree(data []*db.SysMenu, parentID int32) []*dto.MenuUIDto {
var root []*dto.MenuUIDto
for _, item := range data {
if item.ParentID == parentID {
if item.IsList {
temp := &dto.MenuUIDto{
ID: strings.ToLower(item.Url),
Title: item.DisplayName,
Icon: item.Avatar,
Type: 1,
OpenType: "_iframe",
// OpenType: "_component",
Href: item.Url,
}
root = append(root, temp)
} else {
temp := &dto.MenuUIDto{
ID: strconv.Itoa(int(item.ID)),
Title: item.DisplayName,
Icon: item.Avatar,
Type: 0,
}
temp.Children = convertToUITree(data, item.ID)
root = append(root, temp)
}
}
}
return root
}
func uniqueSysMenus(sm []*db.SysMenu) []*db.SysMenu {
res := make([]*db.SysMenu, 0) // 返回的新切片
m1 := make(map[int32]byte) // 用来去重的临时map
for _, v := range sm {
if _, ok := m1[v.ID]; !ok {
m1[v.ID] = 1
res = append(res, v)
}
}
return res
}

View File

@@ -0,0 +1,51 @@
package system
import (
db "management/internal/db/sqlc"
"management/internal/pkg/redis"
"management/internal/pkg/session"
)
type SystemBiz interface {
UserBiz() UserBiz
MenuBiz() MenuBiz
DepartmentBiz() DepartmentBiz
AuditBiz() AuditBiz
ConfigBiz() ConfigBiz
}
type systemBiz struct {
store db.Store
redis redis.IRedis
session session.ISession
}
var _ SystemBiz = (*systemBiz)(nil)
func New(store db.Store, redis redis.IRedis, session session.ISession) *systemBiz {
return &systemBiz{
store: store,
redis: redis,
session: session,
}
}
func (b *systemBiz) UserBiz() UserBiz {
return NewUser(b.store, b.session)
}
func (b *systemBiz) MenuBiz() MenuBiz {
return NewMenu(b.store, b.redis, b.session)
}
func (b *systemBiz) DepartmentBiz() DepartmentBiz {
return NewDepartment(b.store, b.redis, b.session)
}
func (b *systemBiz) AuditBiz() AuditBiz {
return NewAudit(b.store)
}
func (b *systemBiz) ConfigBiz() ConfigBiz {
return NewConfig(b.store, b.redis, b.session)
}

View File

@@ -0,0 +1,117 @@
package system
import (
"context"
"encoding/json"
"errors"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/erpserver/model/req"
"management/internal/global/know"
"management/internal/pkg/crypto"
"management/internal/pkg/session"
)
// UserBiz 定义处理用户请求所需的方法.
type UserBiz interface {
Create(ctx context.Context, req *db.CreateSysUserParams) (*db.SysUser, error)
UserExpansion
}
// UserExpansion 定义用户操作的扩展方法.
type UserExpansion interface {
Login(ctx context.Context, req *req.Login) error
}
// userBiz 是 UserBiz 接口的实现.
type userBiz struct {
store db.Store
session session.ISession
}
// 确保 userBiz 实现了 UserBiz 接口.
var _ UserBiz = (*userBiz)(nil)
func NewUser(store db.Store, session session.ISession) *userBiz {
return &userBiz{
store: store,
session: session,
}
}
func (b *userBiz) Create(ctx context.Context, req *db.CreateSysUserParams) (*db.SysUser, error) {
return b.store.CreateSysUser(ctx, req)
}
func (b *userBiz) Login(ctx context.Context, req *req.Login) error {
log := &db.CreateSysUserLoginLogParams{
CreatedAt: time.Now(),
Email: req.Email,
IsSuccess: false,
RefererUrl: req.Referrer,
Url: req.Url,
Os: req.Os,
Ip: req.Ip,
Browser: req.Browser,
}
user, err := b.store.GetSysUserByEmail(ctx, req.Email)
if err != nil {
log.Message = err.Error()
_ = b.store.CreateSysUserLoginLog(ctx, log)
return err
}
log.UserUuid = user.Uuid
log.Username = user.Username
err = crypto.BcryptComparePassword(user.HashedPassword, req.Password+user.Salt)
if err != nil {
log.Message = "compare password failed"
_ = b.store.CreateSysUserLoginLog(ctx, log)
return errors.New(log.Message)
}
// 登陆成功
if user.RoleID == 0 {
log.Message = "账号没有配置角色, 请联系管理员"
_ = b.store.CreateSysUserLoginLog(ctx, log)
return errors.New(log.Message)
}
sysRole, err := b.store.GetSysRole(ctx, user.RoleID)
if err != nil {
log.Message = "账号配置的角色错误, 请联系管理员"
_ = b.store.CreateSysUserLoginLog(ctx, log)
return errors.New(log.Message)
}
auth := dto.AuthorizeUser{
ID: user.ID,
Uuid: user.Uuid,
Email: user.Email,
Username: user.Username,
RoleID: sysRole.ID,
RoleName: sysRole.Name,
OS: log.Os,
IP: log.Ip,
Browser: log.Browser,
}
gob, err := json.Marshal(auth)
if err != nil {
log.Message = err.Error()
_ = b.store.CreateSysUserLoginLog(ctx, log)
return err
}
b.session.Put(ctx, know.StoreName, gob)
log.IsSuccess = true
log.Message = "登陆成功"
_ = b.store.CreateSysUserLoginLog(ctx, log)
return nil
}

View File

@@ -0,0 +1,56 @@
package common
import (
"net/http"
"management/internal/config"
commonv1 "management/internal/erpserver/biz/v1/common"
"management/internal/pkg/tpl"
)
type CaptchaHandler interface {
Captcha(w http.ResponseWriter, r *http.Request)
}
// captchaHandler 是 CaptchaHandler 接口的实现.
type captchaHandler struct {
conf *config.Captcha
render tpl.Renderer
biz commonv1.CaptchaBiz
}
// 确保 captchaHandler 实现了 CaptchaHandler 接口.
var _ CaptchaHandler = (*captchaHandler)(nil)
func NewCaptchaHandler(conf *config.Captcha, render tpl.Renderer, biz commonv1.CaptchaBiz) *captchaHandler {
return &captchaHandler{
conf: conf,
render: render,
biz: biz,
}
}
type CaptchaResponse struct {
CaptchaID string `json:"captcha_id"`
PicPath string `json:"pic_path"`
CaptchaLength int `json:"captcha_length"`
OpenCaptcha int `json:"open_captcha"`
}
func (h *captchaHandler) Captcha(w http.ResponseWriter, r *http.Request) {
keyLong := h.conf.KeyLong
oc := h.conf.OpenCaptcha
id, b64s, _, err := h.biz.Generate(h.conf.ImgHeight, h.conf.ImgWidth, keyLong, 0.7, 80)
if err != nil {
h.render.JSON(w, tpl.Response{Success: false, Message: "获取验证码失败"})
return
}
rsp := CaptchaResponse{
CaptchaID: id,
PicPath: b64s,
CaptchaLength: keyLong,
OpenCaptcha: oc,
}
h.render.JSON(w, tpl.Response{Success: true, Message: "ok", Data: rsp})
}

View File

@@ -0,0 +1,31 @@
package common
import (
"management/internal/config"
commonv1 "management/internal/erpserver/biz/v1/common"
"management/internal/pkg/tpl"
)
type CommonHandler interface {
CaptchaHandler() CaptchaHandler
}
type commonHandler struct {
conf *config.Config
render tpl.Renderer
biz commonv1.CommonBiz
}
var _ CommonHandler = (*commonHandler)(nil)
func NewCommonHandler(conf *config.Config, render tpl.Renderer, biz commonv1.CommonBiz) *commonHandler {
return &commonHandler{
conf: conf,
render: render,
biz: biz,
}
}
func (h *commonHandler) CaptchaHandler() CaptchaHandler {
return NewCaptchaHandler(&h.conf.Captcha, h.render, h.biz.CaptchaBiz())
}

View File

@@ -0,0 +1,55 @@
package handler
import (
"net/http"
"management/internal/config"
"management/internal/erpserver/biz"
"management/internal/erpserver/handler/common"
"management/internal/erpserver/handler/system"
"management/internal/pkg/session"
"management/internal/pkg/tpl"
)
// IHandler 定义了Handler需要实现的方法.
type IHandler interface {
// 获取 Common Handler 接口.
CommonHandler() common.CommonHandler
// 获取首页
Home(w http.ResponseWriter, req *http.Request)
// 获取 System Handler 接口.
SystemHandler() system.SystemHandler
}
// handler 是 IHandler 的一个具体实现.
type handler struct {
conf *config.Config
render tpl.Renderer
session session.ISession
biz biz.IBiz
}
// 确保 handler 实现了 IHandler 接口.
var _ IHandler = (*handler)(nil)
// NewHandler 创建一个 IHandler 类型的实例.
func NewHandler(conf *config.Config, render tpl.Renderer, session session.ISession, biz biz.IBiz) *handler {
return &handler{
conf: conf,
render: render,
session: session,
biz: biz,
}
}
// CommonHandler 返回一个实现了 CommonHandler 接口的实例.
func (h *handler) CommonHandler() common.CommonHandler {
return common.NewCommonHandler(h.conf, h.render, h.biz.CommonV1())
}
// SystemHandler 返回一个实现了 SystemHandler 接口的实例.
func (h *handler) SystemHandler() system.SystemHandler {
return system.NewSystemHandler(h.render, h.session, h.biz)
}

View File

@@ -0,0 +1,7 @@
package handler
import "net/http"
func (h *handler) Home(w http.ResponseWriter, r *http.Request) {
h.render.HTML(w, r, "home/home.tmpl", nil)
}

View File

@@ -0,0 +1,50 @@
package system
import (
"net/http"
"management/internal/erpserver/biz"
"management/internal/pkg/session"
"management/internal/pkg/tpl"
)
type ConfigHandler interface {
// Add(w http.ResponseWriter, r *http.Request)
// Edit(w http.ResponseWriter, r *http.Request)
// Save(w http.ResponseWriter, r *http.Request)
// List(w http.ResponseWriter, r *http.Request)
ConfigExpansion
}
type ConfigExpansion interface {
Pear(w http.ResponseWriter, r *http.Request)
}
// configHandler 是 ConfigHandler 接口的实现.
type configHandler struct {
render tpl.Renderer
session session.ISession
biz biz.IBiz
}
// 确保 userHandler 实现了 ConfigHandler 接口.
var _ ConfigHandler = (*configHandler)(nil)
func NewConfigHandler(render tpl.Renderer, session session.ISession, biz biz.IBiz) *configHandler {
return &configHandler{
render: render,
session: session,
biz: biz,
}
}
func (h *configHandler) Pear(w http.ResponseWriter, r *http.Request) {
pear, err := h.biz.SystemV1().ConfigBiz().Pear(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
h.render.JSON(w, pear)
}

View File

@@ -0,0 +1,206 @@
package system
import (
"fmt"
"net/http"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/erpserver/biz"
"management/internal/pkg/convertor"
"management/internal/pkg/tpl"
)
type DepartmentHandler interface {
List(w http.ResponseWriter, r *http.Request)
Add(w http.ResponseWriter, r *http.Request)
AddChildren(w http.ResponseWriter, r *http.Request)
Edit(w http.ResponseWriter, r *http.Request)
Save(w http.ResponseWriter, r *http.Request)
Tree(w http.ResponseWriter, r *http.Request)
Refresh(w http.ResponseWriter, r *http.Request)
RebuildParentPath(w http.ResponseWriter, r *http.Request)
}
type departmentHandler struct {
render tpl.Renderer
biz biz.IBiz
}
var _ DepartmentHandler = (*departmentHandler)(nil)
func NewDepartmentHandler(render tpl.Renderer, biz biz.IBiz) *departmentHandler {
return &departmentHandler{
render: render,
biz: biz,
}
}
func (h *departmentHandler) List(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
h.render.HTML(w, r, "department/list.tmpl", nil)
return
} else if r.Method == http.MethodPost {
var q dto.SearchDto
q.SearchStatus = convertor.ConvertInt(r.PostFormValue("status"), 9999)
q.SearchParentID = convertor.ConvertInt(r.PostFormValue("parentId"), 0)
q.SearchName = r.PostFormValue("name")
q.SearchID = convertor.ConvertInt[int64](r.PostFormValue("id"), 0)
q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10)
res, count, err := h.biz.SystemV1().DepartmentBiz().List(r.Context(), q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: count,
Data: res,
}
h.render.JSON(w, data)
return
}
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (h *departmentHandler) Add(w http.ResponseWriter, r *http.Request) {
h.render.HTML(w, r, "department/edit.tmpl", map[string]any{
"Item": &db.SysDepartment{Sort: 6666},
})
}
func (h *departmentHandler) AddChildren(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
parentID := convertor.QueryInt(vars, "parentID", 0)
vm := &db.SysDepartment{ParentID: int32(parentID), Sort: 6666}
h.render.HTML(w, r, "department/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *departmentHandler) Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := convertor.QueryInt[int32](vars, "id", 0)
vm := &db.SysDepartment{Sort: 6666}
if id > 0 {
vm, _ = h.biz.SystemV1().DepartmentBiz().Get(r.Context(), id)
}
h.render.HTML(w, r, "department/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *departmentHandler) Save(w http.ResponseWriter, r *http.Request) {
id := convertor.ConvertInt[int32](r.PostFormValue("ID"), 0)
ParentID := convertor.ConvertInt[int32](r.PostFormValue("ParentID"), 0)
name := r.PostFormValue("Name")
sort := convertor.ConvertInt[int32](r.PostFormValue("Sort"), 6666)
status := convertor.ConvertInt[int32](r.PostFormValue("Status"), 9999)
ctx := r.Context()
var parent *db.SysDepartment
if ParentID > 0 {
var err error
parent, err = h.biz.SystemV1().DepartmentBiz().Get(ctx, ParentID)
if err != nil {
h.render.JSONERR(w, "父级节点错误")
return
}
}
if id == 0 {
arg := db.CreateSysDepartmentParams{
Name: name,
ParentID: ParentID,
ParentPath: fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID),
Status: status,
Sort: sort,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
_, err := h.biz.SystemV1().DepartmentBiz().Create(ctx, &arg)
if err != nil {
if db.IsUniqueViolation(err) {
h.render.JSONERR(w, "部门名称已存在")
return
}
h.render.JSONERR(w, err.Error())
return
}
h.render.JSONOK(w, "添加成功")
} else {
res, err := h.biz.SystemV1().DepartmentBiz().Get(ctx, id)
if err != nil {
h.render.JSONERR(w, err.Error())
return
}
arg := &db.UpdateSysDepartmentParams{
ID: res.ID,
Name: name,
ParentID: ParentID,
ParentPath: fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID),
Status: status,
Sort: sort,
UpdatedAt: time.Now(),
}
_, err = h.biz.SystemV1().DepartmentBiz().Update(ctx, arg)
if err != nil {
h.render.JSONERR(w, err.Error())
return
}
h.render.JSONOK(w, "更新成功")
}
}
func (h *departmentHandler) Tree(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := r.URL.Query()
if vars.Get("type") == "xmselect" {
res, err := h.biz.SystemV1().DepartmentBiz().XmSelect(ctx, 0)
if err != nil {
h.render.JSONERR(w, err.Error())
return
}
h.render.JSON(w, res)
return
} else {
res, err := h.biz.SystemV1().DepartmentBiz().Tree(ctx, 0)
if err != nil {
h.render.JSONERR(w, err.Error())
return
}
h.render.JSON(w, res)
return
}
}
func (h *departmentHandler) Refresh(w http.ResponseWriter, r *http.Request) {
_, err := h.biz.SystemV1().DepartmentBiz().Refresh(r.Context())
if err != nil {
h.render.JSONERR(w, err.Error())
return
}
h.render.JSONOK(w, "刷新成功")
}
func (h *departmentHandler) RebuildParentPath(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := h.biz.SystemV1().DepartmentBiz().RebuildParentPath(ctx)
if err != nil {
h.render.JSONERR(w, err.Error())
return
}
h.render.JSONOK(w, "重建成功")
}

View File

@@ -0,0 +1,53 @@
package system
import (
"encoding/json"
"net/http"
"management/internal/db/model/dto"
"management/internal/erpserver/biz"
"management/internal/global/know"
"management/internal/pkg/session"
"management/internal/pkg/tpl"
)
type MenuHandler interface {
MenuExpansion
}
type MenuExpansion interface {
Menus(w http.ResponseWriter, r *http.Request)
}
type menuHandler struct {
render tpl.Renderer
session session.ISession
biz biz.IBiz
}
var _ MenuHandler = (*menuHandler)(nil)
func NewMenuHandler(render tpl.Renderer, session session.ISession, biz biz.IBiz) *menuHandler {
return &menuHandler{
render: render,
session: session,
biz: biz,
}
}
func (h *menuHandler) Menus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
b := h.session.GetBytes(ctx, know.StoreName)
var u dto.AuthorizeUser
if err := json.Unmarshal(b, &u); err != nil {
h.render.JSONERR(w, err.Error())
return
}
menus, err := h.biz.SystemV1().MenuBiz().RecursiveSysMenus(ctx, u.RoleID)
if err != nil {
h.render.JSONERR(w, err.Error())
return
}
h.render.JSON(w, menus)
}

View File

@@ -0,0 +1,46 @@
package system
import (
"management/internal/erpserver/biz"
"management/internal/pkg/session"
"management/internal/pkg/tpl"
)
type SystemHandler interface {
UserHandler() UserHandler
MenuHandler() MenuHandler
DepartmentHandler() DepartmentHandler
ConfigHandler() ConfigHandler
}
type systemHandler struct {
render tpl.Renderer
session session.ISession
biz biz.IBiz
}
var _ SystemHandler = (*systemHandler)(nil)
func NewSystemHandler(render tpl.Renderer, session session.ISession, biz biz.IBiz) *systemHandler {
return &systemHandler{
render: render,
session: session,
biz: biz,
}
}
func (h *systemHandler) UserHandler() UserHandler {
return NewUserHandler(h.render, h.session, h.biz)
}
func (h *systemHandler) MenuHandler() MenuHandler {
return NewMenuHandler(h.render, h.session, h.biz)
}
func (h *systemHandler) DepartmentHandler() DepartmentHandler {
return NewDepartmentHandler(h.render, h.biz)
}
func (h *systemHandler) ConfigHandler() ConfigHandler {
return NewConfigHandler(h.render, h.session, h.biz)
}

View File

@@ -0,0 +1,127 @@
package system
import (
"encoding/json"
"net/http"
"strings"
"management/internal/db/model/dto"
"management/internal/erpserver/biz"
"management/internal/erpserver/model/req"
"management/internal/global/know"
"management/internal/pkg/session"
"management/internal/pkg/tpl"
"github.com/zhang2092/browser"
)
type UserHandler interface {
Add(w http.ResponseWriter, r *http.Request)
Edit(w http.ResponseWriter, r *http.Request)
Save(w http.ResponseWriter, r *http.Request)
List(w http.ResponseWriter, r *http.Request)
UserExpansion
}
type UserExpansion interface {
Login(w http.ResponseWriter, r *http.Request)
Logout(w http.ResponseWriter, r *http.Request)
}
// userHandler 是 UserHandler 接口的实现.
type userHandler struct {
render tpl.Renderer
session session.ISession
biz biz.IBiz
}
// 确保 userHandler 实现了 UserHandler 接口.
var _ UserHandler = (*userHandler)(nil)
func NewUserHandler(render tpl.Renderer, session session.ISession, biz biz.IBiz) *userHandler {
return &userHandler{
render: render,
session: session,
biz: biz,
}
}
func (h *userHandler) Add(w http.ResponseWriter, r *http.Request) {}
func (h *userHandler) Edit(w http.ResponseWriter, r *http.Request) {}
func (h *userHandler) Save(w http.ResponseWriter, r *http.Request) {}
func (h *userHandler) List(w http.ResponseWriter, r *http.Request) {}
func (h *userHandler) Login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.Method == http.MethodGet {
var user dto.AuthorizeUser
u := h.session.GetBytes(ctx, know.StoreName)
if err := json.Unmarshal(u, &user); err == nil {
// 判断租户是否一致, 一致则刷新令牌,跳转到首页
if err := h.session.RenewToken(ctx); err == nil {
h.session.Put(ctx, know.StoreName, u)
http.Redirect(w, r, "/home.html", http.StatusFound)
return
}
}
h.session.Destroy(ctx)
h.render.HTML(w, r, "oauth/login.tmpl", nil)
return
} else if r.Method == http.MethodPost {
req := &req.Login{
Email: strings.TrimSpace(r.PostFormValue("email")),
Password: strings.TrimSpace(r.PostFormValue("password")),
CaptchaID: strings.TrimSpace(r.PostFormValue("captcha_id")),
Captcha: strings.TrimSpace(r.PostFormValue("captcha")),
Ip: r.RemoteAddr,
Referrer: r.Header.Get("Referer"),
Url: r.URL.RequestURI(),
}
if len(req.Email) == 0 {
h.render.JSON(w, tpl.Response{Success: false, Message: "请填写邮箱"})
return
}
if len(req.Password) == 0 {
h.render.JSON(w, tpl.Response{Success: false, Message: "请填写密码"})
return
}
if len(req.Captcha) == 0 {
h.render.JSON(w, tpl.Response{Success: false, Message: "请填写验证码"})
return
}
if !h.biz.CommonV1().CaptchaBiz().Verify(req.CaptchaID, req.Captcha, true) {
h.render.JSON(w, tpl.Response{Success: false, Message: "验证码错误"})
return
}
br, err := browser.NewBrowser(r.Header.Get("User-Agent"))
if err != nil {
h.render.JSONERR(w, "平台信息获取错误")
return
}
req.Os = br.Platform().Name()
req.Browser = br.Name()
err = h.biz.SystemV1().UserBiz().Login(ctx, req)
if err != nil {
h.render.JSONERR(w, err.Error())
return
}
h.render.JSONOK(w, "login successful")
return
}
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (h *userHandler) Logout(w http.ResponseWriter, r *http.Request) {
h.session.Destroy(r.Context())
http.Redirect(w, r, "/", http.StatusFound)
}

147
internal/erpserver/http.go Normal file
View File

@@ -0,0 +1,147 @@
package erpserver
import (
"net/http"
"management/internal/erpserver/handler"
mw "management/internal/pkg/middleware"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func NewRouter(handler handler.IHandler, mw mw.IMiddleware) *chi.Mux {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
// r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
staticServer := http.FileServer(http.Dir("./web/statics/"))
r.Handle("/statics/*", http.StripPrefix("/statics", staticServer))
uploadServer := http.FileServer(http.Dir("./upload/"))
r.Handle("/upload/*", http.StripPrefix("/upload", uploadServer))
r.Group(func(r chi.Router) {
r.Use(mw.NoSurf) // CSRF
r.Use(mw.LoadSession) // Session
r.Get("/captcha", handler.CommonHandler().CaptchaHandler().Captcha)
r.Get("/", handler.SystemHandler().UserHandler().Login)
r.Post("/login", handler.SystemHandler().UserHandler().Login)
r.Get("/logout", handler.SystemHandler().UserHandler().Logout)
// r.With(auth.Authorize, mw.Audit).Post("/upload/img", commonhandler.UploadImg)
// r.With(auth.Authorize, mw.Audit).Post("/upload/file", commonhandler.UploadFile)
// r.With(auth.Authorize, mw.Audit).Post("/upload/mutilfile", commonhandler.UploadMutilFiles)
r.With(mw.Authorize, mw.Audit).Get("/home.html", handler.Home)
r.With(mw.Authorize).Get("/pear.json", handler.SystemHandler().ConfigHandler().Pear)
r.Route("/system", func(r chi.Router) {
r.Use(mw.Authorize)
// r.Route("/config", func(r chi.Router) {
// r.Use(mw.Audit)
// r.Get("/list", configHandler.List)
// r.Post("/list", configHandler.PostList)
// r.Get("/add", configHandler.Add)
// r.Get("/edit", configHandler.Edit)
// r.Post("/save", configHandler.Save)
// r.Post("/reset_pear", configHandler.ResetPear)
// r.Post("/refresh", configHandler.Refresh)
// })
r.Route("/department", func(r chi.Router) {
r.Use(mw.Audit)
r.Get("/list", handler.SystemHandler().DepartmentHandler().List)
r.Post("/list", handler.SystemHandler().DepartmentHandler().List)
r.Get("/add", handler.SystemHandler().DepartmentHandler().Add)
r.Get("/add_children", handler.SystemHandler().DepartmentHandler().AddChildren)
r.Get("/edit", handler.SystemHandler().DepartmentHandler().Edit)
r.Post("/save", handler.SystemHandler().DepartmentHandler().Save)
r.Post("/tree", handler.SystemHandler().DepartmentHandler().Tree)
r.Post("/refresh", handler.SystemHandler().DepartmentHandler().Refresh)
r.Post("/rebuild_parent_path", handler.SystemHandler().DepartmentHandler().RebuildParentPath)
})
// r.Route("/user", func(r chi.Router) {
// r.Use(mw.Audit)
// userHandler := systemhandler.NewSysUserHandler()
// r.Get("/list", userHandler.List)
// r.Post("/list", userHandler.PostList)
// r.Get("/add", userHandler.Add)
// r.Get("/edit", userHandler.Edit)
// r.Post("/save", userHandler.Save)
// r.Get("/profile", userHandler.Profile)
// r.Post("/xmselect", userHandler.XmSelect)
// })
// r.Route("/login_log", func(r chi.Router) {
// // r.Use(mw.Audit)
// userLoginLogHandler := systemhandler.NewSysUserLoginLogHandler()
// r.Get("/list", userLoginLogHandler.List)
// r.Post("/list", userLoginLogHandler.PostList)
// })
// r.Route("/audit_log", func(r chi.Router) {
// // r.Use(mw.Audit)
// userAuditLogHandler := systemhandler.NewSysAuditLogHandler()
// r.Get("/list", userAuditLogHandler.List)
// r.Post("/list", userAuditLogHandler.PostList)
// })
// r.Route("/role", func(r chi.Router) {
// r.Use(mw.Audit)
// roleHandler := systemhandler.NewSysRoleHandler()
// r.Get("/list", roleHandler.List)
// r.Post("/list", roleHandler.PostList)
// r.Get("/add", roleHandler.Add)
// r.Get("/edit", roleHandler.Edit)
// r.Post("/save", roleHandler.Save)
// r.Post("/dtree", roleHandler.DTree)
// r.Post("/refresh", roleHandler.Refresh)
// r.Post("/rebuild_parent_path", roleHandler.RebuildParentPath)
// r.Post("/refresh_role_menus", roleHandler.RefreshRoleMenus)
// r.Post("/xm_select", roleHandler.XmSelect)
// r.Get("/set_menu", roleHandler.SetMenu)
// r.Post("/set_menu", roleHandler.PostSetMenu)
// })
r.Get("/menus", handler.SystemHandler().MenuHandler().Menus)
// r.Route("/menu", func(r chi.Router) {
// r.Use(mw.Audit)
// r.Get("/tree", menuHandler.Tree)
// r.Get("/list", menuHandler.List)
// r.Post("/list", menuHandler.PostList)
// r.Get("/add", menuHandler.Add)
// r.Get("/add_children", menuHandler.AddChildren)
// r.Get("/edit", menuHandler.Edit)
// r.Post("/save", menuHandler.Save)
// r.Post("/xm_select_tree", menuHandler.XmSelectTree)
// r.Post("/refresh_cache", menuHandler.Refresh)
// })
// // 类别
// r.Route("/category", func(r chi.Router) {
// r.Use(mw.Audit)
// categoryHandler := categoryhandler.NewCategoryHandler()
// r.Get("/list", categoryHandler.List)
// r.Post("/list", categoryHandler.PostList)
// r.Get("/add", categoryHandler.Add)
// r.Get("/add_children", categoryHandler.AddChildren)
// r.Get("/edit", categoryHandler.Edit)
// r.Post("/save", categoryHandler.Save)
// r.Post("/dtree", categoryHandler.DTree)
// r.Post("/xmselect", categoryHandler.XmSelect)
// r.Post("/refresh", categoryHandler.Refresh)
// r.Post("/rebuild_parent_path", categoryHandler.RebuildParentPath)
// })
})
})
return r
}

View File

@@ -0,0 +1,15 @@
package req
type Login struct {
Email string `json:"email"`
Password string `json:"password"`
Captcha string `json:"captcha"`
CaptchaID string `json:"captcha_id"`
// 平台信息
Os string `json:"os"`
Ip string `json:"ip"`
Browser string `json:"browser"`
Referrer string `json:"referrer"`
Url string `json:"url"`
}

View File

@@ -0,0 +1,14 @@
package view
type LayuiTree struct {
ID string `json:"id"`
Title string `json:"title"`
Spread bool `json:"spread"`
Children []*LayuiTree `json:"children"`
}
type XmSelectTree struct {
Name string `json:"name"`
Value string `json:"value"`
Children []*XmSelectTree `json:"children"`
}

View File

@@ -10,4 +10,36 @@ const (
IncomeCategory = "income_category"
ExpenseCategory = "expense_category"
CookieName = "authorize"
StoreName = "authorize_user"
)
var (
// pear admin 配置
PearAdmin = "m:pearjson"
// 所有类别
AllCategories = "m:category:all"
// 所有类别 简单信息
AllCategorySimple = "m:categorysimple:all"
// 类别列表 根据 父id 获取
ListCategoriesByParentID = "m:category:parent_id:%d"
// 所有部门
AllDepartments = "m:department:all"
// 所有菜单
AllMenus = "m:menus:all"
// 递归菜单
RecursiveMenus = "m:rec_menus:%d"
// 根据用户ID获取菜单
AdminMenus = "m:admin_menus:%d"
// 登陆用户的菜单
OwnerMenus = "m:owner_menus:%d"
// 登陆用户的菜单
OwnerMenusMap = "m:owner_menus_map:%d"
// 所有角色
AllRoles = "m:role:all"
)

View File

@@ -2,12 +2,9 @@ package auth
import (
"context"
"encoding/json"
"net/http"
"management/internal/db/model/dto"
"management/internal/global/auth"
"management/internal/pkg/session"
systemservice "management/internal/service/system"
)
@@ -61,23 +58,23 @@ func Authorize(next http.Handler) http.Handler {
}
func isLogin(ctx context.Context) (*dto.AuthorizeUser, bool) {
if exists := session.Exists(ctx, auth.StoreName); exists {
b := session.GetBytes(ctx, auth.StoreName)
var user dto.AuthorizeUser
if err := json.Unmarshal(b, &user); err != nil {
return nil, false
}
return &user, true
}
// if exists := session.Exists(ctx, auth.StoreName); exists {
// b := session.GetBytes(ctx, auth.StoreName)
// var user dto.AuthorizeUser
// if err := json.Unmarshal(b, &user); err != nil {
// return nil, false
// }
// return &user, true
// }
return nil, false
}
func AuthUser(ctx context.Context) dto.AuthorizeUser {
var user dto.AuthorizeUser
if exists := session.Exists(ctx, auth.StoreName); exists {
b := session.GetBytes(ctx, auth.StoreName)
_ = json.Unmarshal(b, &user)
}
// if exists := session.Exists(ctx, auth.StoreName); exists {
// b := session.GetBytes(ctx, auth.StoreName)
// _ = json.Unmarshal(b, &user)
// }
return user
}

View File

@@ -6,6 +6,6 @@ import (
"management/internal/pkg/session"
)
func LoadSession(next http.Handler) http.Handler {
func LoadSession(session session.ISession, next http.Handler) http.Handler {
return session.LoadAndSave(next)
}

View File

@@ -1,6 +1,9 @@
package convertor
import "strconv"
import (
"net/url"
"strconv"
)
func ConvertInt[T int | int16 | int32 | int64](value string, defaultValue T) T {
i, err := strconv.Atoi(value)
@@ -9,3 +12,17 @@ func ConvertInt[T int | int16 | int32 | int64](value string, defaultValue T) T {
}
return T(i)
}
func QueryInt[T int | int16 | int32 | int64](vars url.Values, key string, defaultValue T) T {
v := vars.Get(key)
if len(v) == 0 {
return defaultValue
}
i, err := strconv.Atoi(v)
if err != nil {
return defaultValue
}
return T(i)
}

View File

@@ -11,6 +11,27 @@ import (
"github.com/rs/zerolog/log"
)
func New(prod bool) {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
logRotate := &lumberjack.Logger{
Filename: "./log/run.log", // 日志文件的位置
MaxSize: 10, // 在进行切割之前,日志文件的最大大小(以MB为单位)
MaxBackups: 100, // 保留旧文件的最大个数
MaxAge: 30, // 保留旧文件的最大天数
Compress: true,
}
zerolog.TimeFieldFormat = time.DateTime
log.Logger = log.With().Caller().Logger()
if prod {
log.Logger = log.Output(logRotate)
} else {
consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.DateTime}
multi := zerolog.MultiLevelWriter(consoleWriter, logRotate)
log.Logger = log.Output(multi)
}
}
func Init() {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
logRotate := &lumberjack.Logger{

View File

@@ -0,0 +1,73 @@
package middleware
import (
"context"
"net/http"
"strconv"
"strings"
"time"
db "management/internal/db/sqlc"
"github.com/zhang2092/browser"
)
func (m *middleware) Audit(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func(res http.ResponseWriter, req *http.Request) {
// 记录审计日志
go m.writeLog(req, start)
}(w, r)
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func (m *middleware) writeLog(req *http.Request, start time.Time) {
end := time.Now()
duration := end.Sub(start)
var params string
method := req.Method
if method == "GET" {
params = req.URL.Query().Encode()
} else if method == "POST" {
contentType := req.Header.Get("Content-Type")
if strings.Contains(contentType, "application/json") {
body := make([]byte, req.ContentLength)
req.Body.Read(body)
params = string(body)
} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
params = req.Form.Encode()
}
}
ctx := req.Context()
au := m.AuthUser(ctx)
arg := &db.CreateSysAuditLogParams{
CreatedAt: time.Now(),
Email: au.Email,
Username: au.Username,
UserUuid: au.Uuid,
StartAt: start,
EndAt: end,
Duration: strconv.FormatInt(duration.Milliseconds(), 10),
Url: req.URL.RequestURI(),
Method: method,
Parameters: params,
RefererUrl: req.Header.Get("Referer"),
Ip: req.RemoteAddr,
Remark: "",
}
br, err := browser.NewBrowser(req.Header.Get("User-Agent"))
if err == nil {
arg.Os = br.Platform().Name()
arg.Browser = br.Name()
}
c, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
_ = m.biz.AuditBiz().Create(c, arg)
}

View File

@@ -0,0 +1,81 @@
package middleware
import (
"context"
"encoding/json"
"net/http"
"management/internal/db/model/dto"
"management/internal/global/auth"
)
var defaultMenus = map[string]bool{
"/home.html": true,
"/system/menus": true,
"/upload/img": true,
"/upload/file": true,
"/upload/mutilfile": true,
"/pear.json": true,
}
func (m *middleware) Authorize(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user, ok := m.isLogin(ctx)
if !ok {
http.Redirect(w, r, "/", http.StatusFound)
return
}
if user == nil {
http.Error(w, "user not found", http.StatusUnauthorized)
return
}
// 登陆成功 判断权限
// 默认权限判断
path := r.URL.Path
if b, ok := defaultMenus[path]; ok && b {
next.ServeHTTP(w, r)
return
}
menus, err := m.biz.MenuBiz().MapOwnerMenuByRoleID(ctx, user.RoleID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, ok := menus[path]; ok {
next.ServeHTTP(w, r)
return
}
http.Error(w, "Unauthorized", http.StatusUnauthorized)
}
return http.HandlerFunc(fn)
}
func (m *middleware) isLogin(ctx context.Context) (*dto.AuthorizeUser, bool) {
if exists := m.session.Exists(ctx, auth.StoreName); exists {
b := m.session.GetBytes(ctx, auth.StoreName)
var user dto.AuthorizeUser
if err := json.Unmarshal(b, &user); err != nil {
return nil, false
}
return &user, true
}
return nil, false
}
func (m *middleware) AuthUser(ctx context.Context) dto.AuthorizeUser {
var user dto.AuthorizeUser
if exists := m.session.Exists(ctx, auth.StoreName); exists {
b := m.session.GetBytes(ctx, auth.StoreName)
_ = json.Unmarshal(b, &user)
}
return user
}

View File

@@ -0,0 +1,29 @@
package middleware
import (
"net/http"
systemv1 "management/internal/erpserver/biz/v1/system"
"management/internal/pkg/session"
)
type IMiddleware interface {
Audit(next http.Handler) http.Handler
NoSurf(next http.Handler) http.Handler
LoadSession(next http.Handler) http.Handler
Authorize(next http.Handler) http.Handler
}
type middleware struct {
biz systemv1.SystemBiz
session session.ISession
}
var _ IMiddleware = (*middleware)(nil)
func New(biz systemv1.SystemBiz, session session.ISession) IMiddleware {
return &middleware{
biz: biz,
session: session,
}
}

View File

@@ -0,0 +1,11 @@
package middleware
import (
"net/http"
"github.com/justinas/nosurf"
)
func (m *middleware) NoSurf(next http.Handler) http.Handler {
return nosurf.New(next)
}

View File

@@ -0,0 +1,9 @@
package middleware
import (
"net/http"
)
func (m *middleware) LoadSession(next http.Handler) http.Handler {
return m.session.LoadAndSave(next)
}

View File

@@ -13,31 +13,42 @@ import (
"github.com/redis/go-redis/v9"
)
var (
engine *redis.Client
ErrRedisKeyNotFound = errors.New("redis key not found")
)
var ErrRedisKeyNotFound = errors.New("redis key not found")
// func GetRedis() *redis.Client {
// return rd
// }
type IRedis interface {
Encode(a any) ([]byte, error)
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
Del(ctx context.Context, keys ...string) error
Get(ctx context.Context, key string) (string, error)
GetBytes(ctx context.Context, key string) ([]byte, error)
Scan(ctx context.Context, cursor uint64, match string, count int64) *redis.ScanCmd
Keys(ctx context.Context, pattern string) ([]string, error)
ListKeys(ctx context.Context, pattern string, pageID int, pageSize int) ([]string, int, error)
}
func Init() error {
type redisCache struct {
engine *redis.Client
}
var _ IRedis = (*redisCache)(nil)
func New(conf config.Redis) (*redisCache, error) {
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", config.File.Redis.Host, config.File.Redis.Port),
Password: config.File.Redis.Password,
DB: config.File.Redis.DB,
Addr: fmt.Sprintf("%s:%d", conf.Host, conf.Port),
Password: conf.Password,
DB: conf.DB,
})
_, err := rdb.Ping(context.Background()).Result()
if err != nil {
return err
return nil, err
}
engine = rdb
return nil
return &redisCache{
engine: rdb,
}, nil
}
func Encode(a any) ([]byte, error) {
func (r *redisCache) Encode(a any) ([]byte, error) {
var b bytes.Buffer
if err := gob.NewEncoder(&b).Encode(a); err != nil {
return nil, err
@@ -47,18 +58,18 @@ func Encode(a any) ([]byte, error) {
}
// Set 设置值
func Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
return engine.Set(ctx, key, value, expiration).Err()
func (r *redisCache) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
return r.engine.Set(ctx, key, value, expiration).Err()
}
// Del 删除键值
func Del(ctx context.Context, keys ...string) error {
return engine.Del(ctx, keys...).Err()
func (r *redisCache) Del(ctx context.Context, keys ...string) error {
return r.engine.Del(ctx, keys...).Err()
}
// Get 获取值
func Get(ctx context.Context, key string) (string, error) {
val, err := engine.Get(ctx, key).Result()
func (r *redisCache) Get(ctx context.Context, key string) (string, error) {
val, err := r.engine.Get(ctx, key).Result()
if err == redis.Nil {
return "", ErrRedisKeyNotFound
} else if err != nil {
@@ -69,8 +80,8 @@ func Get(ctx context.Context, key string) (string, error) {
}
// GetBytes 获取值
func GetBytes(ctx context.Context, key string) ([]byte, error) {
val, err := engine.Get(ctx, key).Bytes()
func (r *redisCache) GetBytes(ctx context.Context, key string) ([]byte, error) {
val, err := r.engine.Get(ctx, key).Bytes()
if err == redis.Nil {
return nil, ErrRedisKeyNotFound
} else if err != nil {
@@ -80,16 +91,16 @@ func GetBytes(ctx context.Context, key string) ([]byte, error) {
}
}
func Scan(ctx context.Context, cursor uint64, match string, count int64) *redis.ScanCmd {
return engine.Scan(ctx, cursor, match, count)
func (r *redisCache) Scan(ctx context.Context, cursor uint64, match string, count int64) *redis.ScanCmd {
return r.engine.Scan(ctx, cursor, match, count)
}
func Keys(ctx context.Context, pattern string) ([]string, error) {
return engine.Keys(ctx, pattern).Result()
func (r *redisCache) Keys(ctx context.Context, pattern string) ([]string, error) {
return r.engine.Keys(ctx, pattern).Result()
}
func ListKeys(ctx context.Context, pattern string, pageID int, pageSize int) ([]string, int, error) {
all, err := engine.Keys(ctx, pattern).Result()
func (r *redisCache) ListKeys(ctx context.Context, pattern string, pageID int, pageSize int) ([]string, int, error) {
all, err := r.engine.Keys(ctx, pattern).Result()
if err != nil {
return nil, 0, err
}
@@ -104,7 +115,7 @@ func ListKeys(ctx context.Context, pattern string, pageID int, pageSize int) ([]
for {
var scanResult []string
var err error
scanResult, cursor, err = engine.Scan(ctx, cursor, pattern, int64(pageSize)).Result()
scanResult, cursor, err = r.engine.Scan(ctx, cursor, pattern, int64(pageSize)).Result()
if err != nil {
return nil, count, err
}
@@ -124,3 +135,36 @@ func ListKeys(ctx context.Context, pattern string, pageID int, pageSize int) ([]
}
return keys[startIndex:endIndex], count, nil
}
// ==========================
func Encode(a any) ([]byte, error) {
return nil, nil
}
func Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
return nil
}
func Del(ctx context.Context, keys ...string) error {
return nil
}
func Get(ctx context.Context, key string) (string, error) {
return "", nil
}
func GetBytes(ctx context.Context, key string) ([]byte, error) {
return nil, nil
}
func Scan(ctx context.Context, cursor uint64, match string, count int64) *redis.ScanCmd {
return nil
}
func Keys(ctx context.Context, pattern string) ([]string, error) {
return nil, nil
}
func ListKeys(ctx context.Context, pattern string, pageID int, pageSize int) ([]string, int, error) {
return nil, 0, nil
}

View File

@@ -1,83 +1,83 @@
package session
import (
"context"
"time"
// import (
// "context"
// "time"
"management/internal/pkg/redis"
)
// "management/internal/pkg/redis"
// )
var (
storePrefix = "scs:session:"
ctx = context.Background()
DefaultRedisStore = newRedisStore()
)
// var (
// storePrefix = "scs:session:"
// ctx = context.Background()
// DefaultRedisStore = newRedisStore()
// )
type redisStore struct{}
// type redisStore struct{}
func newRedisStore() *redisStore {
return &redisStore{}
}
// func newRedisStore() *redisStore {
// return &redisStore{}
// }
// Delete should remove the session token and corresponding data from the
// session store. If the token does not exist then Delete should be a no-op
// and return nil (not an error).
func (s *redisStore) Delete(token string) error {
return redis.Del(ctx, storePrefix+token)
}
// // Delete should remove the session token and corresponding data from the
// // session store. If the token does not exist then Delete should be a no-op
// // and return nil (not an error).
// func (s *redisStore) Delete(token string) error {
// return redis.Del(ctx, storePrefix+token)
// }
// Find should return the data for a session token from the store. If the
// session token is not found or is expired, the found return value should
// be false (and the err return value should be nil). Similarly, tampered
// or malformed tokens should result in a found return value of false and a
// nil err value. The err return value should be used for system errors only.
func (s *redisStore) Find(token string) (b []byte, found bool, err error) {
val, err := redis.GetBytes(ctx, storePrefix+token)
if err != nil {
return nil, false, err
} else {
return val, true, nil
}
}
// // Find should return the data for a session token from the store. If the
// // session token is not found or is expired, the found return value should
// // be false (and the err return value should be nil). Similarly, tampered
// // or malformed tokens should result in a found return value of false and a
// // nil err value. The err return value should be used for system errors only.
// func (s *redisStore) Find(token string) (b []byte, found bool, err error) {
// val, err := redis.GetBytes(ctx, storePrefix+token)
// if err != nil {
// return nil, false, err
// } else {
// return val, true, nil
// }
// }
// Commit should add the session token and data to the store, with the given
// expiry time. If the session token already exists, then the data and
// expiry time should be overwritten.
func (s *redisStore) Commit(token string, b []byte, expiry time.Time) error {
// TODO: 这边可以调整时间
exp, err := time.ParseInLocation(time.DateTime, time.Now().Format("2006-01-02")+" 23:59:59", time.Local)
if err != nil {
return err
}
// // Commit should add the session token and data to the store, with the given
// // expiry time. If the session token already exists, then the data and
// // expiry time should be overwritten.
// func (s *redisStore) Commit(token string, b []byte, expiry time.Time) error {
// // TODO: 这边可以调整时间
// exp, err := time.ParseInLocation(time.DateTime, time.Now().Format("2006-01-02")+" 23:59:59", time.Local)
// if err != nil {
// return err
// }
t := time.Now()
expired := exp.Sub(t)
return redis.Set(ctx, storePrefix+token, b, expired)
}
// t := time.Now()
// expired := exp.Sub(t)
// return redis.Set(ctx, storePrefix+token, b, expired)
// }
// All should return a map containing data for all active sessions (i.e.
// sessions which have not expired). The map key should be the session
// token and the map value should be the session data. If no active
// sessions exist this should return an empty (not nil) map.
func (s *redisStore) All() (map[string][]byte, error) {
sessions := make(map[string][]byte)
// // All should return a map containing data for all active sessions (i.e.
// // sessions which have not expired). The map key should be the session
// // token and the map value should be the session data. If no active
// // sessions exist this should return an empty (not nil) map.
// func (s *redisStore) All() (map[string][]byte, error) {
// sessions := make(map[string][]byte)
iter := redis.Scan(ctx, 0, storePrefix+"*", 0).Iterator()
for iter.Next(ctx) {
key := iter.Val()
token := key[len(storePrefix):]
data, exists, err := s.Find(token)
if err != nil {
return nil, err
}
// iter := redis.Scan(ctx, 0, storePrefix+"*", 0).Iterator()
// for iter.Next(ctx) {
// key := iter.Val()
// token := key[len(storePrefix):]
// data, exists, err := s.Find(token)
// if err != nil {
// return nil, err
// }
if exists {
sessions[token] = data
}
}
if err := iter.Err(); err != nil {
return nil, err
}
// if exists {
// sessions[token] = data
// }
// }
// if err := iter.Err(); err != nil {
// return nil, err
// }
return sessions, nil
}
// return sessions, nil
// }

View File

@@ -5,55 +5,68 @@ import (
"net/http"
"time"
"management/internal/config"
db "management/internal/db/sqlc"
"github.com/alexedwards/scs/pgxstore"
"github.com/alexedwards/scs/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
var sessionManager *scs.SessionManager
type ISession interface {
Destroy(ctx context.Context) error
LoadAndSave(next http.Handler) http.Handler
Put(ctx context.Context, key string, val any)
GetBytes(ctx context.Context, key string) []byte
Exists(ctx context.Context, key string) bool
RenewToken(ctx context.Context) error
}
func Init() {
sessionManager = scs.New()
type session struct {
sessionManager *scs.SessionManager
}
func New(pool *pgxpool.Pool, prod bool) ISession {
sessionManager := scs.New()
sessionManager.Lifetime = 24 * time.Hour
sessionManager.IdleTimeout = 2 * time.Hour
sessionManager.Cookie.Name = "token"
sessionManager.Cookie.HttpOnly = true
sessionManager.Cookie.Persist = true
sessionManager.Cookie.SameSite = http.SameSiteStrictMode
sessionManager.Cookie.Secure = config.File.App.Prod
sessionManager.Cookie.Secure = prod
// postgres
// github.com/alexedwards/scs/postgresstore
// sessionManager.Store = postgresstore.New(db)
// pgx
// github.com/alexedwards/scs/pgxstore
sessionManager.Store = pgxstore.New(db.Engine.Pool())
sessionManager.Store = pgxstore.New(pool)
// redis
// sessionManager.Store = newRedisStore()
return &session{
sessionManager: sessionManager,
}
}
func Destroy(ctx context.Context) error {
return sessionManager.Destroy(ctx)
func (s *session) Destroy(ctx context.Context) error {
return s.sessionManager.Destroy(ctx)
}
func LoadAndSave(next http.Handler) http.Handler {
return sessionManager.LoadAndSave(next)
func (s *session) LoadAndSave(next http.Handler) http.Handler {
return s.sessionManager.LoadAndSave(next)
}
func Put(ctx context.Context, key string, val interface{}) {
sessionManager.Put(ctx, key, val)
func (s *session) Put(ctx context.Context, key string, val any) {
s.sessionManager.Put(ctx, key, val)
}
func GetBytes(ctx context.Context, key string) []byte {
return sessionManager.GetBytes(ctx, key)
func (s *session) GetBytes(ctx context.Context, key string) []byte {
return s.sessionManager.GetBytes(ctx, key)
}
func Exists(ctx context.Context, key string) bool {
return sessionManager.Exists(ctx, key)
func (s *session) Exists(ctx context.Context, key string) bool {
return s.sessionManager.Exists(ctx, key)
}
func RenewToken(ctx context.Context) error {
return sessionManager.RenewToken(ctx)
func (s *session) RenewToken(ctx context.Context) error {
return s.sessionManager.RenewToken(ctx)
}

48
internal/pkg/tpl/html.go Normal file
View File

@@ -0,0 +1,48 @@
package tpl
import (
"bytes"
"net/http"
"path/filepath"
"strings"
"management/internal/db/model/dto"
)
type TemplateConfig struct {
Root string
Extension string
Layout string
Partial string
}
type HtmlData struct {
IsAuthenticated bool
AuthorizeUser dto.AuthorizeUser
AuthorizeMenus []*dto.OwnerMenuDto
Data any
}
func (r *render) HTML(w http.ResponseWriter, req *http.Request, tpl string, data map[string]any) {
name := strings.ReplaceAll(tpl, "/", "_")
t, ok := r.templates[name]
if !ok {
http.Error(w, "template is empty", http.StatusInternalServerError)
return
}
hd := r.setDefaultData(req, data)
buf := new(bytes.Buffer)
err := t.ExecuteTemplate(buf, filepath.Base(tpl), hd)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = buf.WriteTo(w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,91 @@
package tpl
import (
"html/template"
"path/filepath"
"strings"
"management/internal/db/model/dto"
)
func (r *render) btnFuncs() map[string]any {
res := make(map[string]any, 3)
res["genBtn"] = func(btns []*dto.OwnerMenuDto, actionNames ...string) template.HTML {
if len(btns) == 0 {
return template.HTML("")
}
var res string
for _, action := range actionNames {
for _, btn := range btns {
btn.Style = strings.ReplaceAll(btn.Style, "pear", "layui")
base := filepath.Base(btn.Url)
if base == action {
res += `<button type="button" class="layui-btn ` + btn.Style + `" lay-event="` + firstLower(action) + `" lay-on="` + firstLower(action) + `">`
if len(btn.Avatar) > 0 {
res += `<i class="` + btn.Avatar + `"></i> `
}
res += btn.DisplayName + `</button>`
}
}
}
return template.HTML(res)
}
res["genLink"] = func(btns []*dto.OwnerMenuDto, actionNames ...string) template.HTML {
if len(btns) == 0 {
return template.HTML("")
}
var res string
for _, action := range actionNames {
for _, btn := range btns {
btn.Style = strings.ReplaceAll(btn.Style, "pear", "layui")
base := filepath.Base(btn.Url)
if base == action {
res += `<button type="button" style="font-size:12px !important;" class="layui-btn ` + btn.Style + `" lay-event="` + firstLower(action) + `" lay-on="` + firstLower(action) + `">`
if len(btn.Avatar) > 0 {
res += `<i class="` + btn.Avatar + `"></i> `
}
res += btn.DisplayName + `</button>`
}
}
}
return template.HTML(res)
}
res["previewPicture"] = func(name string) template.HTML {
var res string
res += `<a><img src="https://school-1251542740.cos.ap-shanghai.myqcloud.com{{` + name + `}}" data-type="1" height="30" width="30" class="preview-all screenshot" onclick="previewPicture('https://school-1251542740.cos.ap-shanghai.myqcloud.com/{{` + name + `}}')"/></a>`
return template.HTML(res)
}
res["submitBtn"] = func(btns []*dto.OwnerMenuDto, actionNames ...string) template.HTML {
if len(btns) == 0 {
return template.HTML("")
}
var res string
for _, action := range actionNames {
for _, btn := range btns {
btn.Style = strings.ReplaceAll(btn.Style, "pear", "layui")
base := filepath.Base(btn.Url)
if base == action {
res += `<button type="submit" class="layui-btn ` + btn.Style + `" lay-submit lay-filter="` + firstLower(action) + `">`
if len(btn.Avatar) > 0 {
res += `<i class="` + btn.Avatar + `"></i> `
}
res += btn.DisplayName + `</button>`
}
}
}
return template.HTML(res)
}
return res
}

View File

@@ -0,0 +1,57 @@
package tpl
import (
"html/template"
"strings"
"time"
)
func (r *render) Methods() map[string]any {
res := make(map[string]any, 1)
res["dateFormat"] = func(dt time.Time) template.HTML {
return template.HTML(dt.Format(time.DateTime))
}
res["today"] = func() template.HTML {
return template.HTML(time.Now().Format("2006-01-02"))
}
res["threeMonth"] = func() template.HTML {
return template.HTML(time.Now().AddDate(0, 3, 0).Format("2006-01-02"))
}
res["yearBegin"] = func() template.HTML {
dt := time.Now()
t := dt.AddDate(0, -int(dt.Month())+1, -dt.Day()+1)
return template.HTML(t.Format("2006-01-02") + " 00:00:00")
}
res["monthBegin"] = func() template.HTML {
dt := time.Now()
t := dt.AddDate(0, 0, -dt.Day()+1)
return template.HTML(t.Format("2006-01-02") + " 00:00:00")
}
res["monthEnd"] = func() template.HTML {
dt := time.Now()
t := dt.AddDate(0, 0, -dt.Day()+1).AddDate(0, 1, -1)
return template.HTML(t.Format("2006-01-02") + " 23:59:59")
}
res["trimSpace"] = func(s string) template.HTML {
return template.HTML(strings.TrimSpace(s))
}
res["expandTags"] = func(s []string) template.HTML {
if len(s) == 0 {
return template.HTML("")
}
if len(s) == 1 && s[0] == "all" {
return template.HTML("")
}
return template.HTML(strings.Join(s, ","))
}
return res
}

57
internal/pkg/tpl/json.go Normal file
View File

@@ -0,0 +1,57 @@
package tpl
import (
"encoding/json"
"net/http"
)
type Response struct {
Success bool `json:"success"`
Message string `json:"msg"`
Data any `json:"data"`
}
type ResponseDtree struct {
Status ResponseDtreeStatus `json:"status"`
Data any `json:"data"`
}
type ResponseDtreeStatus struct {
Code int `json:"code"`
Message string `json:"message"`
}
type ResponseList struct {
Code int `json:"code"`
Message string `json:"msg"`
Count int64 `json:"count"`
Data any `json:"data"`
}
func (r *render) JSONF(w http.ResponseWriter, success bool, message string) {
r.JSON(w, Response{Success: success, Message: message})
}
func (r *render) JSONOK(w http.ResponseWriter, message string) {
r.JSON(w, Response{Success: true, Message: message})
}
func (r *render) JSONERR(w http.ResponseWriter, message string) {
r.JSON(w, Response{Success: false, Message: message})
}
func (r *render) JSON(w http.ResponseWriter, data any) {
v, err := json.Marshal(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err = w.Write(v)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,46 @@
package tpl
import (
"html/template"
"net/http"
systemv1 "management/internal/erpserver/biz/v1/system"
"management/internal/pkg/session"
)
type Renderer interface {
HTML(w http.ResponseWriter, req *http.Request, name string, data map[string]any)
JSON(w http.ResponseWriter, data any)
JSONF(w http.ResponseWriter, success bool, message string)
JSONOK(w http.ResponseWriter, message string)
JSONERR(w http.ResponseWriter, message string)
}
type render struct {
session session.ISession
config *TemplateConfig
templates map[string]*template.Template
menuBiz systemv1.MenuBiz
}
func New(session session.ISession, menuBiz systemv1.MenuBiz) (Renderer, error) {
render := &render{
session: session,
menuBiz: menuBiz,
config: &TemplateConfig{
Root: ".",
Extension: ".tmpl",
Layout: "base",
Partial: "partial",
},
}
templates, err := render.createTemplateCache()
if err != nil {
return nil, err
}
render.templates = templates
return render, nil
}

181
internal/pkg/tpl/util.go Normal file
View File

@@ -0,0 +1,181 @@
package tpl
import (
"context"
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net/http"
"os"
"path/filepath"
"slices"
"strings"
"management/internal/db/model/dto"
"management/internal/global/auth"
templates "management/web/templates/manage"
"github.com/justinas/nosurf"
)
func (r *render) setDefaultData(req *http.Request, data map[string]any) map[string]any {
if data == nil {
data = make(map[string]any)
}
ctx := req.Context()
isAuth := r.session.Exists(ctx, auth.StoreName)
data["IsAuthenticated"] = isAuth
if isAuth {
var authUser dto.AuthorizeUser
u := r.session.GetBytes(ctx, auth.StoreName)
_ = json.Unmarshal(u, &authUser)
data["AuthorizeMenus"] = r.getCurrentPathBtns(ctx, authUser.RoleID, req.URL.Path)
}
token := nosurf.Token(req)
data["CsrfToken"] = token
data["CsrfTokenField"] = template.HTML(fmt.Sprintf(`<input type="hidden" name="csrf_token" value="%s" />`, token))
return data
}
func (r *render) getCurrentPathBtns(ctx context.Context, roleID int32, path string) []*dto.OwnerMenuDto {
var res []*dto.OwnerMenuDto
// 获取当前path的菜单
menu, err := r.menuBiz.GetSysMenuByUrl(ctx, path)
if err != nil {
return res
}
// 获取权限
menus, err := r.menuBiz.ListOwnerMenuByRoleID(ctx, roleID)
if err != nil {
return res
}
for _, item := range menus {
if menu.IsList {
if item.ParentID == menu.ID || item.ID == menu.ID {
res = append(res, item)
}
} else {
if item.ParentID == menu.ParentID {
res = append(res, item)
}
}
}
return res
}
func (r *render) createTemplateCache() (map[string]*template.Template, error) {
cache := make(map[string]*template.Template)
pages, err := getFiles(r.config.Root, r.config.Extension)
if err != nil {
return nil, err
}
layoutAndPartial, err := r.getLayoutAndPartials()
if err != nil {
return nil, err
}
for _, page := range pages {
if strings.HasPrefix(page, "base") || strings.HasSuffix(page, "partial") {
continue
}
name := filepath.Base(page)
pathArr := strings.Split(page, "/")
dir := pathArr[len(pathArr)-2 : len(pathArr)-1]
templateName := fmt.Sprintf("%s_%s", dir[0], name)
ts := template.Must(template.New(templateName).Funcs(r.btnFuncs()).Funcs(r.Methods()).ParseFS(templates.TemplateFS, page))
if err != nil {
return nil, err
}
ts, err = ts.ParseFS(templates.TemplateFS, layoutAndPartial...)
if err != nil {
return nil, err
}
cache[templateName] = ts
}
return cache, nil
}
func (r *render) getLayoutAndPartials() ([]string, error) {
layouts, err := getFiles(r.config.Layout, r.config.Extension)
if err != nil {
return nil, err
}
partials, err := getFiles(r.config.Partial, r.config.Extension)
if err != nil {
return nil, err
}
return slices.Concat(layouts, partials), nil
}
func getFiles(path string, stuffix string) ([]string, error) {
files := make([]string, 0)
b, err := pathExists(templates.TemplateFS, path)
if err != nil {
return nil, err
}
if !b {
return files, nil
}
err = fs.WalkDir(templates.TemplateFS, path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if strings.HasSuffix(path, stuffix) {
files = append(files, path)
}
return nil
})
// err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
// if info == nil {
// return err
// }
// if info.IsDir() {
// return nil
// }
// // 将模板后缀的文件放到列表
// if strings.HasSuffix(path, stuffix) {
// files = append(files, path)
// }
// return nil
// })
return files, err
}
func pathExists(fs fs.FS, path string) (bool, error) {
_, err := fs.Open(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, err
}
func firstLower(s string) string {
if len(s) == 0 {
return s
}
return strings.ToLower(s[:1]) + s[1:]
}

View File

@@ -8,6 +8,7 @@ import (
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/pkg/convertor"
"management/internal/router/manage/util"
categoryservice "management/internal/service/category"
"management/internal/tpl"
@@ -28,12 +29,12 @@ func (h *CategoryHandler) List(w http.ResponseWriter, r *http.Request) {
func (h *CategoryHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchStatus = util.ConvertInt(r.PostFormValue("SearchStatus"), 9999)
q.SearchParentID = util.ConvertInt(r.PostFormValue("SearchParentID"), 0)
q.SearchName = r.PostFormValue("SearchName")
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
q.SearchStatus = convertor.ConvertInt(r.PostFormValue("status"), 9999)
q.SearchParentID = convertor.ConvertInt(r.PostFormValue("parentId"), 0)
q.SearchName = r.PostFormValue("name")
q.SearchID = convertor.ConvertInt[int64](r.PostFormValue("id"), 0)
q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10)
res, count, err := categoryservice.ListCategoriesCondition(r.Context(), q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -194,19 +195,32 @@ func (h *CategoryHandler) DTree(w http.ResponseWriter, r *http.Request) {
}
func (h *CategoryHandler) XmSelect(w http.ResponseWriter, r *http.Request) {
all, err := categoryservice.ListByLetter(r.Context(), r.URL.Query().Get("letter"))
letter := r.URL.Query().Get("letter")
ctx := r.Context()
if len(letter) > 0 {
all, err := categoryservice.ListByLetter(ctx, letter)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
var res []*dto.XmSelectStrDto
for _, v := range all {
res = append(res, &dto.XmSelectStrDto{
Name: v.Name,
Value: strconv.FormatInt(int64(v.ID), 10),
})
}
tpl.JSON(w, res)
return
}
res, err := categoryservice.XmSelectCategory(ctx, 0)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
var res []*dto.XmSelectStrDto
for _, v := range all {
res = append(res, &dto.XmSelectStrDto{
Name: v.Name,
Value: strconv.FormatInt(int64(v.ID), 10),
})
}
tpl.JSON(w, res)
}

View File

@@ -8,9 +8,7 @@ import (
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
authglobal "management/internal/global/auth"
"management/internal/pkg/crypto"
"management/internal/pkg/session"
captchaservice "management/internal/service/captcha"
systemservice "management/internal/service/system"
"management/internal/tpl"
@@ -19,19 +17,19 @@ import (
)
func Login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var user dto.AuthorizeUser
u := session.GetBytes(ctx, authglobal.StoreName)
if err := json.Unmarshal(u, &user); err == nil {
// 判断租户是否一致, 一致则刷新令牌,跳转到首页
if err := session.RenewToken(ctx); err == nil {
session.Put(ctx, authglobal.StoreName, u)
http.Redirect(w, r, "/home.html", http.StatusFound)
return
}
}
// ctx := r.Context()
// var user dto.AuthorizeUser
// u := session.GetBytes(ctx, authglobal.StoreName)
// if err := json.Unmarshal(u, &user); err == nil {
// // 判断租户是否一致, 一致则刷新令牌,跳转到首页
// if err := session.RenewToken(ctx); err == nil {
// session.Put(ctx, authglobal.StoreName, u)
// http.Redirect(w, r, "/home.html", http.StatusFound)
// return
// }
// }
session.Destroy(ctx)
// session.Destroy(ctx)
tpl.HTML(w, r, "oauth/login.tmpl", nil)
}
@@ -122,7 +120,7 @@ func PostLogin(w http.ResponseWriter, r *http.Request) {
Browser: log.Browser,
}
b, err := json.Marshal(auth)
_, err = json.Marshal(auth)
if err != nil {
log.Message = err.Error()
_ = systemservice.CreateSysUserLoginLog(ctx, log)
@@ -130,15 +128,15 @@ func PostLogin(w http.ResponseWriter, r *http.Request) {
return
}
session.Put(ctx, authglobal.StoreName, b)
// session.Put(ctx, authglobal.StoreName, b)
log.IsSuccess = true
log.Message = "登陆成功"
_ = systemservice.CreateSysUserLoginLog(ctx, log)
tpl.JSON(w, tpl.Response{Success: true, Message: "login successful"})
tpl.JSONOK(w, "login successful")
}
func Logout(w http.ResponseWriter, r *http.Request) {
session.Destroy(r.Context())
// session.Destroy(r.Context())
http.Redirect(w, r, "/", http.StatusFound)
}

View File

@@ -6,7 +6,6 @@ import (
"management/internal/middleware/manage/audit"
"management/internal/middleware/manage/auth"
"management/internal/middleware/manage/nosurf"
"management/internal/middleware/manage/session"
budgethandler "management/internal/router/manage/budget"
cachehandler "management/internal/router/manage/cache"
@@ -38,8 +37,8 @@ func NewRouter() *chi.Mux {
r.Handle("/upload/*", http.StripPrefix("/upload", uploadServer))
r.Group(func(r chi.Router) {
r.Use(nosurf.NoSurf) // CSRF
r.Use(session.LoadSession) // Session
r.Use(nosurf.NoSurf) // CSRF
// r.Use(session.LoadSession) // Session
r.Get("/captcha", commonhandler.Captcha)

View File

@@ -1,16 +1,12 @@
package system
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/global/auth"
"management/internal/pkg/session"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
@@ -189,20 +185,20 @@ func (h *SysMenuHandler) Save(w http.ResponseWriter, r *http.Request) {
}
func (h *SysMenuHandler) UserMenus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
b := session.GetBytes(ctx, auth.StoreName)
var u dto.AuthorizeUser
if err := json.Unmarshal(b, &u); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
menus, err := systemservice.RecursiveSysMenus(ctx, u.RoleID)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
// ctx := r.Context()
// b := session.GetBytes(ctx, auth.StoreName)
// var u dto.AuthorizeUser
// if err := json.Unmarshal(b, &u); err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// menus, err := systemservice.RecursiveSysMenus(ctx, u.RoleID)
// if err != nil {
// tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
// return
// }
tpl.JSON(w, menus)
tpl.JSON(w, nil)
}
func (h *SysMenuHandler) XmSelectTree(w http.ResponseWriter, r *http.Request) {

View File

@@ -8,6 +8,7 @@ import (
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/pkg/convertor"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
@@ -25,14 +26,13 @@ func (h *SysRoleHandler) List(w http.ResponseWriter, r *http.Request) {
func (h *SysRoleHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchStatus = util.ConvertInt(r.PostFormValue("SearchStatus"), 9999)
q.SearchParentID = util.ConvertInt(r.PostFormValue("SearchParentID"), 0)
q.SearchName = r.PostFormValue("SearchName")
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
ctx := r.Context()
res, count, err := systemservice.ListSysRoleCondition(ctx, q)
q.SearchStatus = convertor.ConvertInt(r.PostFormValue("status"), 9999)
q.SearchParentID = convertor.ConvertInt(r.PostFormValue("parentId"), 0)
q.SearchName = r.PostFormValue("name")
q.SearchID = convertor.ConvertInt[int64](r.PostFormValue("id"), 0)
q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10)
res, count, err := systemservice.ListSysRoleCondition(r.Context(), q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -141,7 +141,7 @@ func (h *SysRoleHandler) Save(w http.ResponseWriter, r *http.Request) {
func (h *SysRoleHandler) XmSelect(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
res, err := systemservice.XmSelectSysRole(ctx)
res, err := systemservice.XmSelectSysRole(ctx, 0)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"strconv"
"strings"
"time"
"management/internal/db/model/dto"
@@ -19,35 +18,25 @@ func ListCategoriesCondition(ctx context.Context, q dto.SearchDto) ([]*db.Catego
countArg := &db.CountCategoriesConditionParams{
IsStatus: q.SearchStatus != 9999,
Status: int16(q.SearchStatus),
IsParentID: q.SearchParentID != 0,
IsID: q.SearchID != 0,
ID: int32(q.SearchID),
IsParentID: q.SearchParentID != 0 && q.SearchParentID != 1,
ParentID: int32(q.SearchParentID),
Name: q.SearchName,
}
dataArg := &db.ListCategoriesConditionParams{
IsStatus: q.SearchStatus != 9999,
Status: int16(q.SearchStatus),
IsParentID: q.SearchParentID != 0,
IsID: q.SearchID != 0,
ID: int32(q.SearchID),
IsParentID: q.SearchParentID != 0 && q.SearchParentID != 1,
ParentID: int32(q.SearchParentID),
Name: q.SearchName,
Skip: (int32(q.Page) - 1) * int32(q.Rows),
Size: int32(q.Rows),
}
if len(q.SearchKey) > 0 {
switch strings.ToLower(q.SearchName) {
case "id":
id, err := strconv.Atoi(q.SearchKey)
if err == nil {
countArg.IsID = true
countArg.ID = int32(id)
dataArg.IsID = true
dataArg.ID = int32(id)
}
case "name":
countArg.Name = q.SearchKey
dataArg.Name = q.SearchKey
}
}
count, err := db.Engine.CountCategoriesCondition(ctx, countArg)
if err != nil {
return nil, 0, err
@@ -70,6 +59,15 @@ func DTreeCategory(ctx context.Context, id int32) ([]*dto.DTreeDto, error) {
return toDtree(id, all), nil
}
func XmSelectCategory(ctx context.Context, id int32) ([]*dto.XmSelectTreeDto, error) {
all, err := db.Engine.AllCategories(ctx)
if err != nil {
return nil, err
}
return toXmSelectTree(id, all), nil
}
func GetParentCategorySelectLetter(ctx context.Context, letter string) ([]*global.DataDict, error) {
all := AllCategories(ctx)
if len(all) == 0 {
@@ -178,6 +176,25 @@ func toDtree(parentId int32, data []*db.Category) []*dto.DTreeDto {
item.Last = !hasChildren(v.ID, data)
item.ParentId = strconv.FormatInt(int64(v.ParentID), 10)
item.Children = toDtree(v.ID, data)
if v.ParentID == 0 {
item.Spread = true
}
res = append(res, &item)
}
}
return res
}
func toXmSelectTree(parentId int32, data []*db.Category) []*dto.XmSelectTreeDto {
var res []*dto.XmSelectTreeDto
for _, v := range data {
if v.ParentID == parentId {
item := dto.XmSelectTreeDto{
Name: v.Name,
Value: strconv.FormatInt(int64(v.ID), 10),
Children: toXmSelectTree(v.ID, data),
}
res = append(res, &item)
}
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"strconv"
"strings"
"time"
"management/internal/db/model/dto"
@@ -27,36 +26,25 @@ func GetSysRole(ctx context.Context, id int32) (*db.SysRole, error) {
func ListSysRoleCondition(ctx context.Context, q dto.SearchDto) ([]*db.SysRole, int64, error) {
countArg := &db.CountSysRoleConditionParams{
IsStatus: q.SearchStatus != 9999,
Status: int32(q.SearchStatus),
IsParentID: q.SearchParentID != 0,
ParentID: int32(q.SearchParentID),
IsStatus: q.SearchStatus != 9999,
Status: int32(q.SearchStatus),
IsID: q.SearchID != 0,
ID: int32(q.SearchID),
IsParentID: q.SearchParentID != 0,
ParentID: int32(q.SearchParentID),
DisplayName: q.SearchName,
}
dataArg := &db.ListSysRoleConditionParams{
IsStatus: q.SearchStatus != 9999,
Status: int32(q.SearchStatus),
IsParentID: q.SearchParentID != 0,
ParentID: int32(q.SearchParentID),
Skip: (int32(q.Page) - 1) * int32(q.Rows),
Size: int32(q.Rows),
}
if len(q.SearchKey) > 0 {
switch strings.ToLower(q.SearchName) {
case "id":
id, err := strconv.Atoi(q.SearchKey)
if err == nil {
countArg.IsID = true
countArg.ID = int32(id)
dataArg.IsID = true
dataArg.ID = int32(id)
}
case "name":
countArg.DisplayName = q.SearchKey
dataArg.DisplayName = q.SearchKey
}
IsStatus: q.SearchStatus != 9999,
Status: int32(q.SearchStatus),
IsID: q.SearchID != 0,
ID: int32(q.SearchID),
IsParentID: q.SearchParentID != 0,
ParentID: int32(q.SearchParentID),
DisplayName: q.SearchName,
Skip: (int32(q.Page) - 1) * int32(q.Rows),
Size: int32(q.Rows),
}
count, err := db.Engine.CountSysRoleCondition(ctx, countArg)
if err != nil {
@@ -71,18 +59,29 @@ func ListSysRoleCondition(ctx context.Context, q dto.SearchDto) ([]*db.SysRole,
return roles, count, nil
}
func XmSelectSysRole(ctx context.Context) ([]*dto.XmSelectDto, error) {
func XmSelectSysRole(ctx context.Context, id int32) ([]*dto.XmSelectTreeDto, error) {
all, err := db.Engine.AllSysRole(ctx)
if err != nil {
return nil, err
}
var res []*dto.XmSelectDto
for _, item := range all {
res = append(res, &dto.XmSelectDto{Name: item.DisplayName, Value: int(item.ID)})
return toXmSelectTree(id, all), nil
}
func toXmSelectTree(parentId int32, data []*db.SysRole) []*dto.XmSelectTreeDto {
var res []*dto.XmSelectTreeDto
for _, v := range data {
if v.ParentID == parentId {
item := dto.XmSelectTreeDto{
Name: v.Name,
Value: strconv.FormatInt(int64(v.ID), 10),
Children: toXmSelectTree(v.ID, data),
}
res = append(res, &item)
}
}
return res, nil
return res
}
func SetMenu(ctx context.Context, roleID int32, menus []*db.SysMenu) error {

View File

@@ -23,11 +23,11 @@ type HtmlData struct {
Data any
}
func HTML(w http.ResponseWriter, req *http.Request, tpl string, data map[string]any) {
rndr.html(w, req, tpl, data)
func HTML(w http.ResponseWriter, r *http.Request, tpl string, data map[string]any) {
rndr.HTML(w, r, tpl, data)
}
func (r *render) html(w http.ResponseWriter, req *http.Request, tpl string, data map[string]any) {
func (r *render) HTML(w http.ResponseWriter, req *http.Request, tpl string, data map[string]any) {
name := strings.ReplaceAll(tpl, "/", "_")
t, ok := r.templates[name]
if !ok {

View File

@@ -29,22 +29,34 @@ type ResponseList struct {
}
func JSON(w http.ResponseWriter, data any) {
rndr.json(w, data)
rndr.JSON(w, data)
}
func JSONF(w http.ResponseWriter, success bool, message string) {
rndr.json(w, Response{Success: success, Message: message})
rndr.JSONF(w, success, message)
}
func JSONOK(w http.ResponseWriter, message string) {
rndr.json(w, Response{Success: true, Message: message})
rndr.JSONOK(w, message)
}
func JSONERR(w http.ResponseWriter, message string) {
rndr.json(w, Response{Success: false, Message: message})
rndr.JSONERR(w, message)
}
func (r *render) json(w http.ResponseWriter, data any) {
func (r *render) JSONF(w http.ResponseWriter, success bool, message string) {
r.JSON(w, Response{Success: success, Message: message})
}
func (r *render) JSONOK(w http.ResponseWriter, message string) {
r.JSON(w, Response{Success: true, Message: message})
}
func (r *render) JSONERR(w http.ResponseWriter, message string) {
r.JSON(w, Response{Success: false, Message: message})
}
func (r *render) JSON(w http.ResponseWriter, data any) {
v, err := json.Marshal(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@@ -3,26 +3,29 @@ package tpl
import (
"html/template"
"net/http"
"management/internal/pkg/session"
)
var rndr Renderer
func Render() Renderer {
return rndr
}
type Renderer interface {
html(w http.ResponseWriter, req *http.Request, name string, data map[string]any)
json(w http.ResponseWriter, data any)
HTML(w http.ResponseWriter, req *http.Request, name string, data map[string]any)
JSON(w http.ResponseWriter, data any)
JSONF(w http.ResponseWriter, success bool, message string)
JSONOK(w http.ResponseWriter, message string)
JSONERR(w http.ResponseWriter, message string)
}
type render struct {
session session.ISession
config *TemplateConfig
templates map[string]*template.Template
}
func Init() error {
func New(session session.ISession) (Renderer, error) {
render := &render{
session: session,
config: &TemplateConfig{
Root: ".",
Extension: ".tmpl",
@@ -33,15 +36,9 @@ func Init() error {
templates, err := render.createTemplateCache()
if err != nil {
return err
return nil, err
}
render.templates = templates
rndr = render
return nil
}
func InitJson() error {
rndr = &render{}
return nil
return render, nil
}

View File

@@ -14,7 +14,6 @@ import (
"management/internal/db/model/dto"
"management/internal/global/auth"
"management/internal/pkg/session"
systemservice "management/internal/service/system"
templates "management/web/templates/manage"
@@ -27,11 +26,11 @@ func (r *render) setDefaultData(req *http.Request, data map[string]any) map[stri
}
ctx := req.Context()
isAuth := session.Exists(ctx, auth.StoreName)
isAuth := r.session.Exists(ctx, auth.StoreName)
data["IsAuthenticated"] = isAuth
if isAuth {
var authUser dto.AuthorizeUser
u := session.GetBytes(ctx, auth.StoreName)
u := r.session.GetBytes(ctx, auth.StoreName)
_ = json.Unmarshal(u, &authUser)
data["AuthorizeMenus"] = r.getCurrentPathBtns(ctx, authUser.RoleID, req.URL.Path)