This commit is contained in:
2025-06-13 17:23:16 +08:00
parent 3150ba80bc
commit 1b72f51e4a
55 changed files with 3894 additions and 310 deletions

View File

@@ -2,6 +2,7 @@ package erpserver
import (
"management/internal/erpserver/model/system"
v1 "management/internal/erpserver/service/v1"
"github.com/go-chi/chi/v5"
)
@@ -16,6 +17,8 @@ type App struct {
LoginLogRepo system.LoginLogRepository
AuditLogRepo system.AuditLogRepository
AuditLogService v1.AuditLogService
Router *chi.Mux
}
@@ -29,6 +32,8 @@ func NewApp(
LoginLogRepo system.LoginLogRepository,
AuditLogRepo system.AuditLogRepository,
AuditLogService v1.AuditLogService,
Router *chi.Mux,
) App {
return App{
@@ -41,6 +46,8 @@ func NewApp(
LoginLogRepo: LoginLogRepo,
AuditLogRepo: AuditLogRepo,
AuditLogService: AuditLogService,
Router: Router,
}
}

View File

@@ -2,6 +2,7 @@ package handler
import (
"context"
"fmt"
"net/http"
"management/internal/erpserver/model/dto"
@@ -10,7 +11,9 @@ import (
"management/internal/pkg/render"
"management/internal/pkg/session"
"github.com/a-h/templ"
"github.com/drhin/logger"
"go.uber.org/zap"
)
//type RouterGroup interface {
@@ -40,14 +43,14 @@ func NewHandler(
}
// =====================================================================================================================
// middleware 帮助方法
// mid 帮助方法
func (h *Handler) AuthUser(ctx context.Context) dto.AuthorizeUser {
u, err := h.session.GetUser(ctx, know.StoreName)
if err != nil {
return dto.AuthorizeUser{}
}
return *u
return u
}
func (h *Handler) RenewToken(ctx context.Context) error {
@@ -61,6 +64,14 @@ func (h *Handler) Destroy(ctx context.Context) error {
// =====================================================================================================================
// render 帮助方法
func (h *Handler) Render(ctx context.Context, w http.ResponseWriter, t templ.Component) {
if err := t.Render(ctx, w); err != nil {
h.Log.Error(err.Error(), err,
zap.String("templ render", fmt.Sprintf("%v", t)),
)
}
}
func (h *Handler) HTML(w http.ResponseWriter, r *http.Request, name string, data map[string]any) {
h.render.HTML(w, r, name, data)
}

View File

@@ -5,6 +5,7 @@ import (
"management/internal/erpserver/handler"
v1 "management/internal/erpserver/service/v1"
"management/internal/erpserver/templ/home"
)
type HomeHandler struct {
@@ -26,19 +27,22 @@ func NewHomeHandler(
}
func (h *HomeHandler) Home(w http.ResponseWriter, r *http.Request) {
h.HTML(w, r, "home/home.tmpl", nil)
ctx := r.Context()
h.Render(ctx, w, home.Home(ctx))
//h.HTML(w, r, "home/home.tmpl", nil)
}
func (h *HomeHandler) Dashboard(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
auth := h.AuthUser(ctx)
user, _ := h.userService.Get(ctx, auth.ID)
//user, _ := h.userService.Get(ctx, auth.ID)
lt, _ := h.loginLogService.LoginTime(ctx, auth.Email)
c := h.loginLogService.LoginCount(ctx, auth.Email)
h.HTML(w, r, "home/dashboard.tmpl", map[string]any{
"Auth": auth,
"User": user,
"LoginTime": lt,
"LoginCount": c,
})
h.Render(ctx, w, home.Dashboard(ctx, int(c), lt.ThisLoginTime, lt.LastLoginTime))
//h.HTML(w, r, "home/dashboard.tmpl", map[string]any{
// "Auth": auth,
// "User": user,
// "LoginTime": lt,
// "LoginCount": c,
//})
}

View File

@@ -9,6 +9,7 @@ import (
"management/internal/erpserver/model/form"
"management/internal/erpserver/model/system"
v1 "management/internal/erpserver/service/v1"
"management/internal/erpserver/templ/system/role"
"management/internal/pkg/binding"
"management/internal/pkg/convertor"
"management/internal/pkg/render"
@@ -31,7 +32,9 @@ func NewRoleHandler(handler *handler.Handler, roleService v1.RoleService, menuSe
func (h *RoleHandler) List(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.HTML(w, r, "role/list.tmpl", nil)
ctx := r.Context()
h.Render(ctx, w, role.List(ctx))
//h.HTML(w, r, "role/list.tmpl", nil)
case http.MethodPost:
var q dto.SearchDto
q.SearchTimeBegin, q.SearchTimeEnd = convertor.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd"))
@@ -60,30 +63,36 @@ func (h *RoleHandler) List(w http.ResponseWriter, r *http.Request) {
}
func (h *RoleHandler) Add(w http.ResponseWriter, r *http.Request) {
h.HTML(w, r, "role/edit.tmpl", map[string]any{
"Item": &system.Role{Sort: 6666},
})
ctx := r.Context()
h.Render(ctx, w, role.Edit(ctx, &system.Role{Sort: 6666}))
//h.HTML(w, r, "role/edit.tmpl", map[string]any{
// "Item": &system.Role{Sort: 6666},
//})
}
func (h *RoleHandler) AddChildren(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
parentID := convertor.QueryInt[int32](vars, "parentID", 0)
vm := &system.Role{ParentID: parentID, Sort: 6666}
h.HTML(w, r, "role/edit.tmpl", map[string]any{
"Item": vm,
})
ctx := r.Context()
h.Render(ctx, w, role.Edit(ctx, vm))
//h.HTML(w, r, "role/edit.tmpl", map[string]any{
// "Item": vm,
//})
}
func (h *RoleHandler) Edit(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := r.URL.Query()
id := convertor.QueryInt[int32](vars, "id", 0)
vm := &system.Role{Sort: 6666}
if id > 0 {
vm, _ = h.roleService.Get(r.Context(), id)
vm, _ = h.roleService.Get(ctx, id)
}
h.HTML(w, r, "role/edit.tmpl", map[string]any{
"Item": vm,
})
h.Render(ctx, w, role.Edit(ctx, vm))
//h.HTML(w, r, "role/edit.tmpl", map[string]any{
// "Item": vm,
//})
}
func (h *RoleHandler) Save(w http.ResponseWriter, r *http.Request) {
@@ -158,32 +167,32 @@ func (h *RoleHandler) RefreshRoleMenus(w http.ResponseWriter, r *http.Request) {
// 获取需要刷新的角色ID
roleID := convertor.ConvertInt[int32](r.PostFormValue("roleID"), 0)
role, err := h.roleService.Get(ctx, roleID)
roleModel, err := h.roleService.Get(ctx, roleID)
if err != nil {
h.JSONErr(w, err.Error())
return
}
if role == nil {
if roleModel == nil {
h.JSONErr(w, "角色不存在")
return
}
// 刷新角色菜单 (角色所拥有的菜单集合)
_, err = h.menuService.SetListByRoleID(ctx, role.ID)
_, err = h.menuService.SetListByRoleID(ctx, roleModel.ID)
if err != nil {
h.JSONErr(w, err.Error())
return
}
// 刷新角色菜单 (角色所拥有的菜单集合)
_, err = h.menuService.SetListByRoleIDToMap(ctx, role.ID)
_, err = h.menuService.SetListByRoleIDToMap(ctx, roleModel.ID)
if err != nil {
h.JSONErr(w, err.Error())
return
}
// 刷新角色菜单树 (pear admin layui 使用的格式)
_, err = h.menuService.SetOwerMenus(ctx, role.ID)
_, err = h.menuService.SetOwerMenus(ctx, roleModel.ID)
if err != nil {
h.JSONErr(w, err.Error())
return
@@ -220,7 +229,7 @@ func (h *RoleHandler) SetMenu(w http.ResponseWriter, r *http.Request) {
h.JSONErr(w, "角色异常, 请刷新重试")
return
}
role, err := h.roleService.Get(ctx, id)
roleModel, err := h.roleService.Get(ctx, id)
if err != nil {
h.JSONErr(w, err.Error())
return
@@ -248,7 +257,7 @@ func (h *RoleHandler) SetMenu(w http.ResponseWriter, r *http.Request) {
}
rms = append(rms, &system.RoleMenu{
RoleID: role.ID,
RoleID: roleModel.ID,
MenuID: menu.ID,
})
}

View File

@@ -3,7 +3,7 @@ package system
//
//import (
// v1 "management/internal/erpserver/service/v1"
// mi "management/internal/pkg/middleware"
// mi "management/internal/pkg/mid"
// "management/internal/pkg/session"
//
// "github.com/go-chi/chi/v5"

View File

@@ -10,6 +10,7 @@ import (
"management/internal/erpserver/model/form"
systemmodel "management/internal/erpserver/model/system"
v1 "management/internal/erpserver/service/v1"
"management/internal/erpserver/templ/auth"
"management/internal/pkg/binding"
"management/internal/pkg/convertor"
"management/internal/pkg/render"
@@ -176,7 +177,9 @@ func (h *UserHandler) Login(w http.ResponseWriter, r *http.Request) {
}
_ = h.Destroy(ctx)
h.HTML(w, r, "oauth/login.tmpl", nil)
component := auth.Login(ctx)
h.Render(ctx, w, component)
//h.HTML(w, r, "oauth/login.tmpl", nil)
case http.MethodPost:
defer func(Body io.ReadCloser) {
err := Body.Close()

View File

@@ -6,7 +6,7 @@ import (
"management/internal/erpserver/handler/common"
"management/internal/erpserver/handler/system"
v1 "management/internal/erpserver/service/v1"
mi "management/internal/pkg/middleware"
mi "management/internal/pkg/mid"
"management/internal/pkg/session"
static "management/web/statics"
@@ -35,7 +35,7 @@ func NewHTTPServer(
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
// r.Use(middleware.Logger)
// r.Use(mid.Logger)
r.Use(middleware.Recoverer)
//staticServer := http.FileServer(http.Dir("./web/statics/"))
@@ -47,6 +47,7 @@ func NewHTTPServer(
r.Group(func(r chi.Router) {
r.Use(mi.NoSurf) // CSRF
r.Use(mi.NoSurfContext) // CSRF Store Context
r.Use(mi.LoadSession(sm)) // Session
r.Get("/", userHandler.Login)
@@ -63,14 +64,14 @@ func NewHTTPServer(
r.Get("/pear.json", configHandler.Pear)
r.Route("/upload", func(r chi.Router) {
r.Use(mi.Audit(sm, auditLogService, log))
r.Use(mi.Audit(sm, log))
r.Get("/img", uploadHandler.Img)
r.Get("/file", uploadHandler.File)
r.Get("/multi_files", uploadHandler.MultiFiles)
})
r.Route("/system", func(r chi.Router) {
r.Use(mi.Audit(sm, auditLogService, log))
r.Use(mi.Audit(sm, log))
r.Get("/menus", menuHandler.Menus)

View File

@@ -9,6 +9,7 @@ type AuthorizeUser struct {
Uuid uuid.UUID `json:"uuid"`
Email string `json:"email"`
Username string `json:"username"`
Avatar string `json:"avatar"`
RoleID int32 `json:"role_id"`
RoleName string `json:"role_name"`
OS string `json:"os"`

View File

@@ -12,6 +12,7 @@ import (
type AuditLogRepository interface {
Create(ctx context.Context, obj *AuditLog) error
BatchCreate(ctx context.Context, objs []*AuditLog) error
List(ctx context.Context, q dto.SearchDto) ([]*AuditLog, int64, error)
}

View File

@@ -22,6 +22,10 @@ func (s *auditLogRepository) Create(ctx context.Context, obj *system.AuditLog) e
return s.repo.DB(ctx).Create(obj).Error
}
func (s *auditLogRepository) BatchCreate(ctx context.Context, objs []*system.AuditLog) error {
return s.repo.DB(ctx).Create(objs).Error
}
func (s *auditLogRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) {
query := s.repo.DB(ctx).
Model(&system.AuditLog{}).

View File

@@ -8,14 +8,14 @@ import (
"time"
"management/internal/erpserver/model/view"
"management/internal/pkg/redis"
"management/internal/pkg/cache"
)
func GetCacheExpire() time.Duration {
return time.Hour*6 + time.Duration(rand.Intn(600))*time.Second // 6小时±10分钟
}
func GetOrSetCache(ctx context.Context, redis redis.Cache, key string, expire time.Duration, getData func() (any, error), result any) error {
func GetOrSetCache(ctx context.Context, redis cache.Cache, key string, expire time.Duration, getData func() (any, error), result any) error {
if data, err := redis.GetBytes(ctx, key); err == nil {
return json.Unmarshal(data, result)
}

View File

@@ -8,7 +8,7 @@ import (
"management/internal/erpserver/model/system"
"management/internal/erpserver/model/view"
"management/internal/erpserver/repository"
"management/internal/pkg/redis"
"management/internal/pkg/cache"
"management/internal/pkg/session"
"github.com/drhin/logger"
@@ -18,14 +18,14 @@ type Service struct {
Log *logger.Logger
Tx repository.Transaction
Session session.Manager
Redis redis.Cache
Redis cache.Cache
}
func NewService(
log *logger.Logger,
tx repository.Transaction,
session session.Manager,
redis redis.Cache,
redis cache.Cache,
) *Service {
return &Service{
Log: log,
@@ -72,6 +72,7 @@ type LoginLogService interface {
type AuditLogService interface {
Create(ctx context.Context, req *system.AuditLog) error
BatchCreate(ctx context.Context, objs []*system.AuditLog) error
List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error)
}

View File

@@ -24,6 +24,10 @@ func (b *auditLogService) Create(ctx context.Context, req *system.AuditLog) erro
return b.repo.Create(ctx, req)
}
func (b *auditLogService) BatchCreate(ctx context.Context, objs []*system.AuditLog) error {
return b.repo.BatchCreate(ctx, objs)
}
func (b *auditLogService) List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) {
return b.repo.List(ctx, q)
}

View File

@@ -179,11 +179,12 @@ func (s *userService) login(ctx context.Context, req *form.Login) error {
}
func (s *userService) loginSuccess(ctx context.Context, user *system.User, req *form.Login) error {
return s.Session.PutUser(ctx, know.StoreName, &dto.AuthorizeUser{
return s.Session.PutUser(ctx, know.StoreName, dto.AuthorizeUser{
ID: user.ID,
Uuid: user.Uuid,
Email: user.Email,
Username: user.Username,
Avatar: user.Avatar,
RoleID: user.Role.ID,
RoleName: user.Role.DisplayName,
OS: req.Os,

View File

@@ -0,0 +1,150 @@
package auth
import (
"context"
"management/internal/pkg/mid"
)
templ Login(ctx context.Context) {
{{ token := mid.GetCsrfToken(ctx) }}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>登录</title>
<link rel="icon" type="image/x-icon" href="/statics/favicon.ico">
<link rel="stylesheet" href="/statics/component/pear/css/pear.css" />
<link rel="stylesheet" href="/statics/admin/css/other/login.css" />
<link rel="stylesheet" href="/statics/admin/css/variables.css" />
</head>
<body>
<div class="login-page" style="background-image: url('/statics/admin/images/background.svg')">
<div class="layui-row">
<div class="layui-col-sm6 login-bg layui-hide-xs">
<img class="login-bg-img" src="/statics/admin/images/banner.png" alt="" />
</div>
<div class="layui-col-sm6 layui-col-xs12 login-form">
<div class="layui-form">
<div class="form-center">
<div class="form-center-box">
<div class="top-log-title">
<img class="top-log" src="/statics/favicon.ico" alt="" />
<span>Pear Admin 4.0</span>
</div>
<div class="top-desc">
以 超 乎 想 象 的 速 度 构 建 内 部 工 具
</div>
<div style="margin-top: 30px;">
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-username"></i>
</div>
<input placeholder="邮 箱 " name="email" type="email" value="1185230223@qq.com"
lay-verify="required" hover class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-password"></i>
</div>
<input placeholder="密 码 " name="password" type="password" value="secret"
lay-verify="required" hover class="layui-input" />
</div>
</div>
<div class="tab-log-verification">
<div class="verification-text">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-auz"></i>
</div>
<input name="captcha" type="text" lay-verify="required" value=""
placeholder="验证码" autocomplete="off" class="layui-input">
<input type="hidden" id="captcha_id" name="captcha_id" />
</div>
</div>
<img id="captcha" src="/captcha" class="verification-img"
style="cursor: pointer;" />
</div>
<div class="login-btn" style="margin-top: 15px;">
<button type="button" lay-submit lay-filter="login"
class="layui-btn pear-btn-success login">
登录</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 资 源 引 入 -->
<script src="/statics/component/layui/layui.js"></script>
<script src="/statics/component/pear/pear.js"></script>
<script>
layui.use(['jquery', 'form', 'button', 'popup'], function () {
var $ = layui.jquery;
var form = layui.form;
var button = layui.button;
var popup = layui.popup;
$(function () {
$('#captcha').click();
})
$('#captcha').click(function () {
$.ajax({
url: '/captcha',
method: 'get',
success: function (j) {
if (j.success) {
$('#captcha').attr('src', j.data.pic_path);
$('#captcha_id').val(j.data.captcha_id);
} else {
console.log(j)
}
}
});
});
// 登 录 提 交
form.on('submit(login)', function (data) {
let loading = button.load({
elem: '.login',
});
data.field.csrf_token = '{{ token }}';
$.ajax({
url: '/login',
type: 'post',
dataType: 'json',
data: data.field,
success: function (obj) {
if (obj.success) {
loading.stop(function () {
popup.success("登录成功", function () {
location.href = "/home.html"
});
});
} else {
loading.stop(function () {
popup.failure(obj.msg);
});
}
},
error: function (ex) {
loading.stop(function () {
popup.failure('网络异常,请刷新重试');
});
}
});
return false;
});
})
</script>
</body>
</html>
}

View File

@@ -0,0 +1,59 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898
package auth
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"context"
"management/internal/pkg/mid"
)
func Login(ctx context.Context) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
token := mid.GetCsrfToken(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"><title>登录</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/statics/favicon.ico\"><link rel=\"stylesheet\" href=\"/statics/component/pear/css/pear.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/other/login.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/variables.css\"></head><body><div class=\"login-page\" style=\"background-image: url('/statics/admin/images/background.svg')\"><div class=\"layui-row\"><div class=\"layui-col-sm6 login-bg layui-hide-xs\"><img class=\"login-bg-img\" src=\"/statics/admin/images/banner.png\" alt=\"\"></div><div class=\"layui-col-sm6 layui-col-xs12 login-form\"><div class=\"layui-form\"><div class=\"form-center\"><div class=\"form-center-box\"><div class=\"top-log-title\"><img class=\"top-log\" src=\"/statics/favicon.ico\" alt=\"\"> <span>Pear Admin 4.0</span></div><div class=\"top-desc\">以 超 乎 想 象 的 速 度 构 建 内 部 工 具</div><div style=\"margin-top: 30px;\"><div class=\"layui-form-item\"><div class=\"layui-input-wrap\"><div class=\"layui-input-prefix\"><i class=\"layui-icon layui-icon-username\"></i></div><input placeholder=\"邮 箱 \" name=\"email\" type=\"email\" value=\"1185230223@qq.com\" lay-verify=\"required\" hover class=\"layui-input\"></div></div><div class=\"layui-form-item\"><div class=\"layui-input-wrap\"><div class=\"layui-input-prefix\"><i class=\"layui-icon layui-icon-password\"></i></div><input placeholder=\"密 码 \" name=\"password\" type=\"password\" value=\"secret\" lay-verify=\"required\" hover class=\"layui-input\"></div></div><div class=\"tab-log-verification\"><div class=\"verification-text\"><div class=\"layui-input-wrap\"><div class=\"layui-input-prefix\"><i class=\"layui-icon layui-icon-auz\"></i></div><input name=\"captcha\" type=\"text\" lay-verify=\"required\" value=\"\" placeholder=\"验证码\" autocomplete=\"off\" class=\"layui-input\"> <input type=\"hidden\" id=\"captcha_id\" name=\"captcha_id\"></div></div><img id=\"captcha\" src=\"/captcha\" class=\"verification-img\" style=\"cursor: pointer;\"></div><div class=\"login-btn\" style=\"margin-top: 15px;\"><button type=\"button\" lay-submit lay-filter=\"login\" class=\"layui-btn pear-btn-success login\">登录</button></div></div></div></div></div></div></div></div><!-- 资 源 引 入 --><script src=\"/statics/component/layui/layui.js\"></script><script src=\"/statics/component/pear/pear.js\"></script><script>\n \t layui.use(['jquery', 'form', 'button', 'popup'], function () {\n \t\t\tvar $ = layui.jquery;\n \t\t\tvar form = layui.form;\n \t\t\tvar button = layui.button;\n \t\t\tvar popup = layui.popup;\n\n \t\t\t$(function () {\n \t\t\t\t$('#captcha').click();\n \t\t\t})\n\n \t\t\t$('#captcha').click(function () {\n \t\t\t\t$.ajax({\n \t\t\t\t\turl: '/captcha',\n \t\t\t\t\tmethod: 'get',\n \t\t\t\t\tsuccess: function (j) {\n \t\t\t\t\t\tif (j.success) {\n \t\t\t\t\t\t\t$('#captcha').attr('src', j.data.pic_path);\n \t\t\t\t\t\t\t$('#captcha_id').val(j.data.captcha_id);\n \t\t\t\t\t\t} else {\n \t\t\t\t\t\t\tconsole.log(j)\n \t\t\t\t\t\t}\n \t\t\t\t\t}\n \t\t\t\t});\n \t\t\t});\n\n \t\t\t// 登 录 提 交\n \t\t\tform.on('submit(login)', function (data) {\n \t\t\t\tlet loading = button.load({\n \t\t\t\t\telem: '.login',\n \t\t\t\t});\n\n \t\t\t\tdata.field.csrf_token = '")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/auth/login.templ`, Line: 119, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "';\n \t\t\t\t$.ajax({\n \t\t\t\t\turl: '/login',\n \t\t\t\t\ttype: 'post',\n \t\t\t\t\tdataType: 'json',\n \t\t\t\t\tdata: data.field,\n \t\t\t\t\tsuccess: function (obj) {\n \t\t\t\t\t\tif (obj.success) {\n \t\t\t\t\t\t\tloading.stop(function () {\n \t\t\t\t\t\t\t\tpopup.success(\"登录成功\", function () {\n \t\t\t\t\t\t\t\t\tlocation.href = \"/home.html\"\n \t\t\t\t\t\t\t\t});\n \t\t\t\t\t\t\t});\n \t\t\t\t\t\t} else {\n \t\t\t\t\t\t\tloading.stop(function () {\n \t\t\t\t\t\t\t\tpopup.failure(obj.msg);\n \t\t\t\t\t\t\t});\n \t\t\t\t\t\t}\n \t\t\t\t\t},\n \t\t\t\t\terror: function (ex) {\n \t\t\t\t\t\tloading.stop(function () {\n \t\t\t\t\t\t\tpopup.failure('网络异常,请刷新重试');\n \t\t\t\t\t\t});\n \t\t\t\t\t}\n \t\t\t\t});\n \t\t\t\treturn false;\n \t\t\t});\n \t\t})\n </script></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -0,0 +1,79 @@
package base
import (
"context"
tpl "github.com/a-h/templ"
)
templ Base(ctx context.Context, style, script tpl.Component) {
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>component</title>
<link rel="icon" type="image/x-icon" href="/statics/favicon.ico">
<link rel="stylesheet" href="/statics/component/pear/css/pear.css" />
<link rel="stylesheet" href="/statics/admin/css/admin.css" />
<link rel="stylesheet" href="/statics/admin/css/admin.dark.css" />
<link rel="stylesheet" href="/statics/admin/css/variables.css" />
<link rel="stylesheet" href="/statics/admin/css/reset.css" />
<link rel="stylesheet" href="/statics/admin/css/style.css" />
@style
</head>
<body class="pear-container">
{ children... }
<script src="/statics/component/layui/layui.js"></script>
<script src="/statics/component/pear/pear.js"></script>
<script src="/statics/component/pear/help.js"></script>
<script src="/statics/js/function/public.js"></script>
<script src="/statics/js/upload.js"></script>
<script>
layui.use(['admin', 'jquery', 'popup', 'drawer'], function () {
var $ = layui.jquery;
var admin = layui.admin;
var popup = layui.popup;
// 读取 localStorage 主题颜色值,初始化 iframe 主题
darkTheme();
themeColor();
// iframe 页监听 localStorage [pear 框架主题颜色] 变化。发生变化,同步更新 iframe 显示效果
window.addEventListener('storage', function (event) {
if (event.key === 'dark') {
darkTheme();
}
if (event.key === 'theme-color') {
themeColor();
}
});
// 夜间模式切换
function darkTheme() {
var $pearAdmin = $(".pear-admin"); // 主体
var $layuiCard = $('.layui-card'); // 编辑页
if (localStorage.getItem('dark') === 'true') {
$pearAdmin.addClass("pear-admin-dark");
$layuiCard.addClass('layui-card-dark');
} else {
$pearAdmin.removeClass("pear-admin-dark");
$layuiCard.removeClass('layui-card-dark');
}
}
// 主题颜色切换
function themeColor() {
const variableKey = "--global-primary-color";
const variableVal = localStorage.getItem("theme-color-color");
document.documentElement.style.setProperty(variableKey, variableVal);
}
})
</script>
@script
</body>
</html>
}

View File

@@ -0,0 +1,70 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898
package base
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"context"
tpl "github.com/a-h/templ"
)
func Base(ctx context.Context, style, script tpl.Component) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"><title>component</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/statics/favicon.ico\"><link rel=\"stylesheet\" href=\"/statics/component/pear/css/pear.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/admin.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/admin.dark.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/variables.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/reset.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/style.css\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = style.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</head><body class=\"pear-container\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script src=\"/statics/component/layui/layui.js\"></script><script src=\"/statics/component/pear/pear.js\"></script><script src=\"/statics/component/pear/help.js\"></script><script src=\"/statics/js/function/public.js\"></script><script src=\"/statics/js/upload.js\"></script><script>\n layui.use(['admin', 'jquery', 'popup', 'drawer'], function () {\n var $ = layui.jquery;\n var admin = layui.admin;\n var popup = layui.popup;\n\n // 读取 localStorage 主题颜色值,初始化 iframe 主题\n darkTheme();\n themeColor();\n\n // iframe 页监听 localStorage [pear 框架主题颜色] 变化。发生变化,同步更新 iframe 显示效果\n window.addEventListener('storage', function (event) {\n if (event.key === 'dark') {\n darkTheme();\n }\n if (event.key === 'theme-color') {\n themeColor();\n }\n });\n\n // 夜间模式切换\n function darkTheme() {\n var $pearAdmin = $(\".pear-admin\"); // 主体\n var $layuiCard = $('.layui-card'); // 编辑页\n\n if (localStorage.getItem('dark') === 'true') {\n $pearAdmin.addClass(\"pear-admin-dark\");\n $layuiCard.addClass('layui-card-dark');\n } else {\n $pearAdmin.removeClass(\"pear-admin-dark\");\n $layuiCard.removeClass('layui-card-dark');\n }\n }\n\n // 主题颜色切换\n function themeColor() {\n const variableKey = \"--global-primary-color\";\n const variableVal = localStorage.getItem(\"theme-color-color\");\n document.documentElement.style.setProperty(variableKey, variableVal);\n }\n })\n </script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = script.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -0,0 +1,140 @@
package component
import (
"path/filepath"
"strings"
"management/internal/erpserver/model/dto"
"github.com/a-h/templ"
)
func TemplBtn(buttons []dto.OwnerMenuDto, searchBtn bool, actionNames ...string) templ.Component {
var res string
if len(actionNames) == 0 {
return templ.Raw(res)
}
if len(buttons) == 0 {
return templ.Raw(res)
}
res = `<script type="text/html" id="toolbar">`
res += GenBtn(buttons, actionNames...)
if searchBtn {
res += `
<button type="button" lay-event="search" lay-on="search" class="layui-btn layui-btn-primary layui-btn-sm">
<i class="layui-icon layui-icon-search"></i>
</button>`
}
res += `</script>`
return templ.Raw(res)
}
func TemplLink(buttons []dto.OwnerMenuDto, actionNames ...string) templ.Component {
var res string
if len(actionNames) == 0 {
return templ.Raw(res)
}
if len(buttons) == 0 {
return templ.Raw(res)
}
res = `<script type="text/html" id="actionBox">`
res += GenLink(buttons, actionNames...)
res += `</script>`
return templ.Raw(res)
}
func GenBtn(buttons []dto.OwnerMenuDto, actionNames ...string) string {
var res string
if len(buttons) == 0 {
return res
}
if len(actionNames) == 0 {
return res
}
for _, action := range actionNames {
for _, btn := range buttons {
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 res
}
func GenLink(buttons []dto.OwnerMenuDto, actionNames ...string) string {
if len(buttons) == 0 {
return ""
}
if len(actionNames) == 0 {
return ""
}
var res string
for _, action := range actionNames {
for _, btn := range buttons {
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 res
}
func SubmitBtn(buttons []dto.OwnerMenuDto, actionNames ...string) templ.Component {
var res string
if len(buttons) == 0 {
return templ.Raw(res)
}
if len(actionNames) == 0 {
return templ.Raw(res)
}
for _, action := range actionNames {
for _, btn := range buttons {
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 templ.Raw(res)
}
func firstLower(s string) string {
if len(s) == 0 {
return s
}
return strings.ToLower(s[:1]) + s[1:]
}

View File

@@ -0,0 +1,38 @@
package home
import (
"context"
"time"
"management/internal/pkg/mid"
)
templ Dashboard(ctx context.Context, loginCount int, thisLoginTime, lastLoginTime time.Time) {
{{ user := mid.GetUser(ctx) }}
<div class="layui-row h-all">
<div class="layui-col-md12" style="padding: 10px;">
<div class="layui-panel userinfo">
<div class="logo">
@avatar(user.Avatar)
</div>
<div class="info">
@info(user.Username, user.RoleName, loginCount, thisLoginTime, lastLoginTime)
</div>
</div>
</div>
</div>
}
templ avatar(avatar string) {
<img src={ avatar } alt="logo">
}
templ info(username, roleName string, loginCount int, thisLoginTime, lastLoginTime time.Time) {
<p class="name">欢迎您,{ username } ({ roleName })</p>
if loginCount == 1 {
<p>这是您第 1 次登录,本次登录日期:{ thisLoginTime.Format(time.DateTime) },如果不是您本人登录,请及时修改密码 。</p>
} else {
<p>这是您第 { loginCount } 次登录,本次登录日期:{ thisLoginTime.Format(time.DateTime) }
上次登录日期:{ lastLoginTime.Format(time.DateTime) },如果不是您本人登录,请及时修改密码 。</p>
}
}

View File

@@ -0,0 +1,224 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898
package home
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"context"
"time"
"management/internal/pkg/mid"
)
func Dashboard(ctx context.Context, loginCount int, thisLoginTime, lastLoginTime time.Time) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
user := mid.GetUser(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"layui-row h-all\"><div class=\"layui-col-md12\" style=\"padding: 10px;\"><div class=\"layui-panel userinfo\"><div class=\"logo\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = avatar(user.Avatar).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><div class=\"info\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = info(user.Username, user.RoleName, loginCount, thisLoginTime, lastLoginTime).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func avatar(avatar string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(avatar)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 27, Col: 21}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" alt=\"logo\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func info(username, roleName string, loginCount int, thisLoginTime, lastLoginTime time.Time) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<p class=\"name\">欢迎您,")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 31, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " (")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(roleName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 31, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, ")</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if loginCount == 1 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<p>这是您第 1 次登录,本次登录日期:")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(thisLoginTime.Format(time.DateTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 33, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, ",如果不是您本人登录,请及时修改密码 。</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p>这是您第 ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(loginCount)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 35, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " 次登录,本次登录日期:")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(thisLoginTime.Format(time.DateTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 35, Col: 109}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " 上次登录日期:")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(lastLoginTime.Format(time.DateTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 36, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, ",如果不是您本人登录,请及时修改密码 。</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -0,0 +1,174 @@
package home
import (
"context"
"management/internal/pkg/mid"
)
templ Home(ctx context.Context) {
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>管理后台</title>
<link rel="icon" type="image/x-icon" href="/statics/favicon.ico">
<link rel="stylesheet" href="/statics/component/pear/css/pear.css" />
<link rel="stylesheet" href="/statics/admin/css/admin.css" />
<link rel="stylesheet" href="/statics/admin/css/admin.dark.css" />
<link rel="stylesheet" href="/statics/admin/css/variables.css" />
<link rel="stylesheet" href="/statics/admin/css/reset.css" />
<link rel="stylesheet" href="/statics/admin/css/style.css" />
</head>
<body class="layui-layout-body pear-admin">
<!-- 布 局 框 架 -->
<div class="layui-layout layui-layout-admin">
<!-- 顶 部 样 式 -->
<div class="layui-header">
<!-- 菜 单 顶 部 -->
<div class="layui-logo">
<!-- 图 标 -->
<img class="logo" alt="" src="">
<!-- 标 题 -->
<span class="title"></span>
</div>
<!-- 顶 部 左 侧 功 能 -->
<ul class="layui-nav layui-layout-left">
<li class="collapse layui-nav-item">
<a href="#" class="layui-icon layui-icon-shrink-right"> </a>
</li>
<li class="refresh layui-nav-item">
<a href="#" class="layui-icon layui-icon-refresh-1"> </a>
</li>
</ul>
<!-- 多 系 统 菜 单 -->
<div id="control" class="layui-layout-control"></div>
<!-- 顶 部 右 侧 菜 单 -->
<ul class="layui-nav layui-layout-right">
<!-- <li class="layui-nav-item layui-hide-xs"><a href="#"
class="menuSearch layui-icon layui-icon-search"></a></li>
<li class="layui-nav-item layui-hide-xs message"></li> -->
<li class="layui-nav-item layui-hide-xs"><a href="#"
class="fullScreen layui-icon layui-icon-screen-full"></a></li>
<li class="layui-nav-item user">
<!-- 头 像 -->
<a class="layui-icon layui-icon-username" href="javascript:;"></a>
<!-- 功 能 菜 单 -->
<dl class="layui-nav-child">
{{ user := mid.GetUser(ctx) }}
if user.ID > 0 {
<dd>
<a user-menu-url="/system/user/profile" user-menu-id="profile"
user-menu-title="基本资料">基本资料</a>
</dd>
<dd>
<a href="javascript:void(0);" title={ user.Username } class="logout">注销登录</a>
</dd>
}
</dl>
</li>
<!-- 主 题 配 置 -->
<li class="layui-nav-item setting"><a href="#" class="layui-icon layui-icon-more-vertical"></a></li>
</ul>
</div>
<!-- 侧 边 区 域 -->
<div class="layui-side layui-bg-black">
<!-- 菜 单 顶 部 -->
<div class="layui-logo">
<!-- 图 标 -->
<img class="logo">
<!-- 标 题 -->
<span class="title"></span>
</div>
<!-- 菜 单 内 容 -->
<div class="layui-side-scroll">
<div id="side"></div>
</div>
</div>
<!-- 视 图 页 面 -->
<div class="layui-body" style="padding: 0">
<!-- 内 容 页 面 -->
<div id="content"></div>
</div>
<!-- 页脚 -->
<div class="layui-footer layui-text"></div>
<!-- 遮 盖 层 -->
<div class="pear-cover"></div>
<!-- 加 载 动 画 -->
<div class="loader-wrapper">
<!-- 动 画 对 象 -->
<div class="loader"></div>
</div>
</div>
<!-- 移 动 端 便 捷 操 作 -->
<div class="pear-collapsed-pe collapse">
<a href="#" class="layui-icon layui-icon-shrink-right"></a>
</div>
<script src="/statics/component/layui/layui.js"></script>
<script src="/statics/component/pear/pear.js"></script>
<script>
layui.use(['admin', 'jquery', 'popup', 'drawer'], function () {
const $ = layui.jquery;
const admin = layui.admin;
const popup = layui.popup;
// yml | json | api
admin.setConfigurationPath("/pear.json");
admin.render();
// 登出逻辑
admin.logout(function () {
window.location.href = '/logout';
// 清空 tabs 缓存
return new Promise((resolve) => {
resolve(true)
});
})
// 读取 localStorage 主题颜色值,初始化 iframe 主题
//darkTheme();
//themeColor();
// iframe 页监听 localStorage [pear 框架主题颜色] 变化。发生变化,同步更新 iframe 显示效果
// window.addEventListener('storage', function (event) {
// if (event.key === 'dark') {
// darkTheme();
// }
// if (event.key === 'theme-color') {
// themeColor();
// }
// });
// 夜间模式切换
// function darkTheme() {
// var $pearAdmin = $(".pear-admin"); // 主体
// var $layuiCard = $('.layui-card'); // 编辑页
// if (localStorage.getItem('dark') === 'true') {
// $pearAdmin.addClass("pear-admin-dark");
// $layuiCard.addClass('layui-card-dark');
// } else {
// $pearAdmin.removeClass("pear-admin-dark");
// $layuiCard.removeClass('layui-card-dark');
// }
// }
// 主题颜色切换
// function themeColor() {
// const variableKey = "--global-primary-color";
// const variableVal = localStorage.getItem("theme-color-color");
// document.documentElement.style.setProperty(variableKey, variableVal);
// }
// 消息点击回调
// admin.message(function (id, title, context, form) { });
})
</script>
</body>
</html>
}

View File

@@ -0,0 +1,70 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898
package home
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"context"
"management/internal/pkg/mid"
)
func Home(ctx context.Context) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"><title>管理后台</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/statics/favicon.ico\"><link rel=\"stylesheet\" href=\"/statics/component/pear/css/pear.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/admin.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/admin.dark.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/variables.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/reset.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/style.css\"></head><body class=\"layui-layout-body pear-admin\"><!-- 布 局 框 架 --><div class=\"layui-layout layui-layout-admin\"><!-- 顶 部 样 式 --><div class=\"layui-header\"><!-- 菜 单 顶 部 --><div class=\"layui-logo\"><!-- 图 标 --><img class=\"logo\" alt=\"\" src=\"\"><!-- 标 题 --><span class=\"title\"></span></div><!-- 顶 部 左 侧 功 能 --><ul class=\"layui-nav layui-layout-left\"><li class=\"collapse layui-nav-item\"><a href=\"#\" class=\"layui-icon layui-icon-shrink-right\"></a></li><li class=\"refresh layui-nav-item\"><a href=\"#\" class=\"layui-icon layui-icon-refresh-1\"></a></li></ul><!-- 多 系 统 菜 单 --><div id=\"control\" class=\"layui-layout-control\"></div><!-- 顶 部 右 侧 菜 单 --><ul class=\"layui-nav layui-layout-right\"><!-- <li class=\"layui-nav-item layui-hide-xs\"><a href=\"#\"\n class=\"menuSearch layui-icon layui-icon-search\"></a></li>\n <li class=\"layui-nav-item layui-hide-xs message\"></li> --><li class=\"layui-nav-item layui-hide-xs\"><a href=\"#\" class=\"fullScreen layui-icon layui-icon-screen-full\"></a></li><li class=\"layui-nav-item user\"><!-- 头 像 --><a class=\"layui-icon layui-icon-username\" href=\"javascript:;\"></a><!-- 功 能 菜 单 --><dl class=\"layui-nav-child\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
user := mid.GetUser(ctx)
if user.ID > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<dd><a user-menu-url=\"/system/user/profile\" user-menu-id=\"profile\" user-menu-title=\"基本资料\">基本资料</a></dd><dd><a href=\"javascript:void(0);\" title=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/home.templ`, Line: 68, Col: 91}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" class=\"logout\">注销登录</a></dd>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</dl></li><!-- 主 题 配 置 --><li class=\"layui-nav-item setting\"><a href=\"#\" class=\"layui-icon layui-icon-more-vertical\"></a></li></ul></div><!-- 侧 边 区 域 --><div class=\"layui-side layui-bg-black\"><!-- 菜 单 顶 部 --><div class=\"layui-logo\"><!-- 图 标 --><img class=\"logo\"><!-- 标 题 --><span class=\"title\"></span></div><!-- 菜 单 内 容 --><div class=\"layui-side-scroll\"><div id=\"side\"></div></div></div><!-- 视 图 页 面 --><div class=\"layui-body\" style=\"padding: 0\"><!-- 内 容 页 面 --><div id=\"content\"></div></div><!-- 页脚 --><div class=\"layui-footer layui-text\"></div><!-- 遮 盖 层 --><div class=\"pear-cover\"></div><!-- 加 载 动 画 --><div class=\"loader-wrapper\"><!-- 动 画 对 象 --><div class=\"loader\"></div></div></div><!-- 移 动 端 便 捷 操 作 --><div class=\"pear-collapsed-pe collapse\"><a href=\"#\" class=\"layui-icon layui-icon-shrink-right\"></a></div><script src=\"/statics/component/layui/layui.js\"></script><script src=\"/statics/component/pear/pear.js\"></script><script>\n layui.use(['admin', 'jquery', 'popup', 'drawer'], function () {\n const $ = layui.jquery;\n const admin = layui.admin;\n const popup = layui.popup;\n\n // yml | json | api\n admin.setConfigurationPath(\"/pear.json\");\n\n admin.render();\n\n // 登出逻辑\n admin.logout(function () {\n window.location.href = '/logout';\n // 清空 tabs 缓存\n return new Promise((resolve) => {\n resolve(true)\n });\n })\n\n // 读取 localStorage 主题颜色值,初始化 iframe 主题\n //darkTheme();\n //themeColor();\n\n // iframe 页监听 localStorage [pear 框架主题颜色] 变化。发生变化,同步更新 iframe 显示效果\n // window.addEventListener('storage', function (event) {\n // if (event.key === 'dark') {\n // darkTheme();\n // }\n // if (event.key === 'theme-color') {\n // themeColor();\n // }\n // });\n\n // 夜间模式切换\n // function darkTheme() {\n // var $pearAdmin = $(\".pear-admin\"); // 主体\n // var $layuiCard = $('.layui-card'); // 编辑页\n\n // if (localStorage.getItem('dark') === 'true') {\n // $pearAdmin.addClass(\"pear-admin-dark\");\n // $layuiCard.addClass('layui-card-dark');\n // } else {\n // $pearAdmin.removeClass(\"pear-admin-dark\");\n // $layuiCard.removeClass('layui-card-dark');\n // }\n // }\n\n // 主题颜色切换\n // function themeColor() {\n // const variableKey = \"--global-primary-color\";\n // const variableVal = localStorage.getItem(\"theme-color-color\");\n // document.documentElement.style.setProperty(variableKey, variableVal);\n // }\n\n // 消息点击回调\n // admin.message(function (id, title, context, form) { });\n })\n </script></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -0,0 +1,17 @@
package minifyjs
import (
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/js"
)
func Compile(raw string) string {
m := minify.New()
m.AddFunc("text/javascript", js.Minify)
minified, err := m.String("text/javascript", raw)
if err != nil {
return raw // 失败时返回原始代码
}
return minified
}

View File

@@ -0,0 +1,212 @@
package role
import (
"context"
"time"
"management/internal/erpserver/templ/base"
"management/internal/pkg/mid"
"management/internal/erpserver/templ/component"
"management/internal/erpserver/model/system"
)
templ Edit(ctx context.Context, item *system.Role) {
@base.Base(ctx, editCss(), editJs(ctx, item)) {
{{ meuns := mid.GetCurMenus(ctx) }}
{{ ht := mid.GetHtmlCsrfToken(ctx) }}
<div class="layui-body layui-bg-gray">
<div class="layui-card">
<form class="layui-form">
@ht
<input type="hidden" id="id" name="id" value={ item.ID } />
<div class="layui-tab layui-tab-card">
<ul class="layui-tab-title">
<li class="layui-this">基础信息</li>
<li>其它</li>
</ul>
<div class="layui-tab-content">
<!-- 基础信息 -->
<div class="layui-tab-item layui-show">
if item.ID > 0 {
<div class="layui-form-item">
<div class="layui-form-label">ID</div>
<div class="layui-form-label" style="width:400px;text-align:left;">
{ item.ID }
</div>
</div>
}
<div class="layui-form-item">
<div class="layui-form-label">上级</div>
<div class="layui-input-inline" style="width:300px;">
<ul id="roleTree" class="dtree organizationTree"></ul>
</div>
</div>
<div class="layui-form-item">
<div class="layui-form-label">名称</div>
<div class="layui-input-inline" style="width:300px;">
<input type="text" id="name" name="name" value={ item.Name } lay-verify="required"
autocomplete="off" placeholder="请输入名称" class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<div class="layui-form-label">显示名称</div>
<div class="layui-input-inline" style="width:300px;">
<input type="text" id="display_name" name="display_name" value={ item.DisplayName }
lay-verify="required" autocomplete="off" placeholder="请输入显示名称"
class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态标识</label>
<div class="layui-input-inline">
<select id="status" name="status">
if item.Status == 0 {
<option value="0" selected>正常</option>
<option value="-1">删除</option>
} else if item.Status == -1 {
<option value="0">正常</option>
<option value="-1" selected>删除</option>
}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-inline">
<input type="number" id="sort" name="sort" value={ item.Sort } lay-affix="number"
min="1" class="layui-input" />
</div>
</div>
</div>
<!-- 其它 -->
<div class="layui-tab-item">
if item.ID > 0 {
<div class="layui-form-item">
<div class="layui-form-label">创建时间</div>
<div class="layui-form-label" style="width:400px;text-align:left;">
{ item.CreatedAt.Format(time.DateTime) }
</div>
</div>
<div class="layui-form-item">
<div class="layui-form-label">更新时间</div>
<div class="layui-form-label" style="width:400px;text-align:left;">
{ item.UpdatedAt.Format(time.DateTime) }
</div>
</div>
}
</div>
</div>
</div>
<div class="layui-form-item layui-fixbar btn-fixbar-box">
<div class="layui-input-block">
@component.SubmitBtn(meuns, "save")
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" lay-on="close">
<i class="layui-icon layui-icon-close"></i>关闭
</button>
</div>
</div>
</form>
</div>
</div>
}
}
templ editCss() {
<style>
.layui-body {
padding: 15px;
left: 0;
}
</style>
}
templ editJs(ctx context.Context, item *system.Role) {
{{ token := mid.GetCsrfToken(ctx) }}
<script>
layui.use(['jquery', 'form', 'xmSelect', 'util'], function () {
let $ = layui.jquery;
let form = layui.form;
let xmSelect = layui.xmSelect;
let util = layui.util;
getRoleTree();
// 表单提交
form.on('submit(save)', function (data) {
$.ajax({
url: '/system/role/save',
type: 'post',
dataType: 'json',
data: data.field,
success: function (result) {
if (result.success) {
layer.msg(result.msg, { icon: 1, time: 1000 }, function () {
parent.layer.close(parent.layer.getFrameIndex(window.name)); // 关闭当前页
parent.layui.table.reload("tablelist", {
page: { curr: 1 },
});
});
} else {
parent.layer.msg(result.msg, { icon: 2 })
}
}
});
return false;
});
// 事件绑定
util.on("lay-on", {
"close": function () {
window.parent.layer.close(parent.layer.getFrameIndex(window.name));
}
});
function getRoleTree() {
$.ajax({
url: "/system/role/data?type=xm_select_tree",
type: 'post',
dataType: 'json',
headers: { 'X-CSRF-Token': '{{ token }}' },
success: function (res) {
xmSelect.render({
el: '#roleTree',
// 工具栏
toolbar: {
show: true
},
radio: true,
clickClose: true,
tips: '请选择父级',
filterable: true,
data: res,
name: 'parent_id',
initValue: ['{{ item.ParentID }}'],
tree: {
show: true,
//非严格模式
strict: false,
//默认展开节点的数组, 为 true 时, 展开所有节点
expandedKeys: [1],
},
on: function (data) { },
});
},
error: function (err) {
// 处理请求错误
console.log('请求出错:', err);
}
});
}
});
</script>
}

View File

@@ -0,0 +1,295 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898
package role
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"context"
"time"
"management/internal/erpserver/model/system"
"management/internal/erpserver/templ/base"
"management/internal/erpserver/templ/component"
"management/internal/pkg/mid"
)
func Edit(ctx context.Context, item *system.Role) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
meuns := mid.GetCurMenus(ctx)
ht := mid.GetHtmlCsrfToken(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"layui-body layui-bg-gray\"><div class=\"layui-card\"><form class=\"layui-form\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = ht.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<input type=\"hidden\" id=\"id\" name=\"id\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(item.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 21, Col: 74}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><div class=\"layui-tab layui-tab-card\"><ul class=\"layui-tab-title\"><li class=\"layui-this\">基础信息</li><li>其它</li></ul><div class=\"layui-tab-content\"><!-- 基础信息 --><div class=\"layui-tab-item layui-show\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.ID > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"layui-form-item\"><div class=\"layui-form-label\">ID</div><div class=\"layui-form-label\" style=\"width:400px;text-align:left;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 36, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"layui-form-item\"><div class=\"layui-form-label\">上级</div><div class=\"layui-input-inline\" style=\"width:300px;\"><ul id=\"roleTree\" class=\"dtree organizationTree\"></ul></div></div><div class=\"layui-form-item\"><div class=\"layui-form-label\">名称</div><div class=\"layui-input-inline\" style=\"width:300px;\"><input type=\"text\" id=\"name\" name=\"name\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 51, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" lay-verify=\"required\" autocomplete=\"off\" placeholder=\"请输入名称\" class=\"layui-input\"></div></div><div class=\"layui-form-item\"><div class=\"layui-form-label\">显示名称</div><div class=\"layui-input-inline\" style=\"width:300px;\"><input type=\"text\" id=\"display_name\" name=\"display_name\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(item.DisplayName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 59, Col: 121}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" lay-verify=\"required\" autocomplete=\"off\" placeholder=\"请输入显示名称\" class=\"layui-input\"></div></div><div class=\"layui-form-item\"><label class=\"layui-form-label\">状态标识</label><div class=\"layui-input-inline\"><select id=\"status\" name=\"status\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.Status == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<option value=\"0\" selected>正常</option> <option value=\"-1\">删除</option>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if item.Status == -1 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<option value=\"0\">正常</option> <option value=\"-1\" selected>删除</option>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</select></div></div><div class=\"layui-form-item\"><label class=\"layui-form-label\">排序</label><div class=\"layui-input-inline\"><input type=\"number\" id=\"sort\" name=\"sort\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(item.Sort)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 83, Col: 100}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" lay-affix=\"number\" min=\"1\" class=\"layui-input\"></div></div></div><!-- 其它 --><div class=\"layui-tab-item\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.ID > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"layui-form-item\"><div class=\"layui-form-label\">创建时间</div><div class=\"layui-form-label\" style=\"width:400px;text-align:left;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.CreatedAt.Format(time.DateTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 95, Col: 78}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></div><div class=\"layui-form-item\"><div class=\"layui-form-label\">更新时间</div><div class=\"layui-form-label\" style=\"width:400px;text-align:left;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(item.UpdatedAt.Format(time.DateTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 101, Col: 78}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></div></div><div class=\"layui-form-item layui-fixbar btn-fixbar-box\"><div class=\"layui-input-block\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = component.SubmitBtn(meuns, "save").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<button type=\"button\" class=\"layui-btn layui-btn-primary layui-btn-sm\" lay-on=\"close\"><i class=\"layui-icon layui-icon-close\"></i>关闭</button></div></div></form></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = base.Base(ctx, editCss(), editJs(ctx, item)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func editCss() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<style>\n .layui-body {\n padding: 15px;\n left: 0;\n }\n </style>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func editJs(ctx context.Context, item *system.Role) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
token := mid.GetCsrfToken(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<script>\n layui.use(['jquery', 'form', 'xmSelect', 'util'], function () {\n let $ = layui.jquery;\n let form = layui.form;\n let xmSelect = layui.xmSelect;\n let util = layui.util;\n\n getRoleTree();\n\n // 表单提交\n form.on('submit(save)', function (data) {\n $.ajax({\n url: '/system/role/save',\n type: 'post',\n dataType: 'json',\n data: data.field,\n success: function (result) {\n if (result.success) {\n layer.msg(result.msg, { icon: 1, time: 1000 }, function () {\n parent.layer.close(parent.layer.getFrameIndex(window.name)); // 关闭当前页\n parent.layui.table.reload(\"tablelist\", {\n page: { curr: 1 },\n });\n });\n } else {\n parent.layer.msg(result.msg, { icon: 2 })\n }\n }\n });\n\n return false;\n });\n\n // 事件绑定\n util.on(\"lay-on\", {\n \"close\": function () {\n window.parent.layer.close(parent.layer.getFrameIndex(window.name));\n }\n });\n\n function getRoleTree() {\n $.ajax({\n url: \"/system/role/data?type=xm_select_tree\",\n type: 'post',\n dataType: 'json',\n headers: { 'X-CSRF-Token': '")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var12, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 179, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "' },\n success: function (res) {\n xmSelect.render({\n el: '#roleTree',\n // 工具栏\n toolbar: {\n show: true\n },\n radio: true,\n clickClose: true,\n tips: '请选择父级',\n filterable: true,\n data: res,\n name: 'parent_id',\n initValue: ['")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var13, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(item.ParentID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 193, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "'],\n tree: {\n show: true,\n //非严格模式\n strict: false,\n //默认展开节点的数组, 为 true 时, 展开所有节点\n expandedKeys: [1],\n },\n on: function (data) { },\n });\n },\n error: function (err) {\n // 处理请求错误\n console.log('请求出错:', err);\n }\n });\n }\n });\n </script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -0,0 +1,314 @@
package role
import (
"context"
"management/internal/erpserver/templ/base"
"management/internal/pkg/mid"
"management/internal/erpserver/templ/component"
)
templ List(ctx context.Context) {
@base.Base(ctx, listCss(), listJs(ctx)) {
{{ meuns := mid.GetCurMenus(ctx) }}
@component.TemplBtn(meuns, true, "add", "refresh_cache", "rebuild_parent_path")
@component.TemplLink(meuns, "edit", "set_menu", "refresh_role_menus")
<div class="search-layer" id="search-layer" style="display: none;">
<input type="hidden" id="parentId" name="parentId" value="0" />
<div class="layui-form layui-row">
<div class="layui-col-xs12 layui-col-sm12 layui-col-md4">
<div class="layui-form-column">
<label for="status" class="tips">状态</label>
<select id="status">
<option value="0">正常</option>
<option value="-1">删除</option>
<option value="9999">全部</option>
</select>
</div>
</div>
<div class="layui-col-xs12 layui-col-sm12 layui-col-md4">
<div class="layui-form-column">
<label for="id" class="tips">编号</label>
<input type="text" id="id" placeholder="请输入编号" class="layui-input">
</div>
</div>
<div class="layui-col-xs12 layui-col-sm12 layui-col-md4">
<div class="layui-form-column">
<label for="name" class="tips">名称</label>
<input type="text" id="name" placeholder="请输入名称" class="layui-input">
</div>
</div>
</div>
</div>
<div class="layui-row h-all">
<div class="layui-col-md2 h-all">
<div class="own-left-pannel">
<div id="roleTree" class="own-tree"></div>
</div>
</div>
<div class="layui-col-md10">
<div class="own-pannel">
<table id="tablelist" lay-filter="tablelist"></table>
</div>
</div>
</div>
}
}
templ listCss() {
}
templ listJs(ctx context.Context) {
{{ token := mid.GetCsrfToken(ctx) }}
<script>
layui.use(['jquery', 'table', 'form', 'tree', 'util'], function () {
let $ = layui.jquery;
let table = layui.table;
let form = layui.form;
let tree = layui.tree;
let util = layui.util;
getRoleTree();
// 加载列表
table.render({
elem: '#tablelist',
url: "/system/role/list",
method: "POST",
headers: { 'X-CSRF-Token': '{{ token }}' },
where: getQueryParams(),
height: function () {
return $(window).height() - 22;
},
page: true,
limit: 15,
limits: [15, 30, 45, 60, 75, 90],
cols: [[
{ field: 'display_name', title: '显示名称', align: 'left', width: 160, fixed: 'left' },
{ field: 'name', title: '名称', align: 'left', width: 150, fixed: 'left' },
{
field: 'status', title: '状态', align: 'center', width: 90, templet: function (row) {
switch (row.status) {
case 0: return '正常';
case -1: return '删除';
default: return '其它';
}
return '';
}
},
{ field: 'sort', title: '排序', align: 'center', width: 60 },
{ field: 'created_at', title: '创建时间', align: 'center', width: 160, templet: function (d) { return !d.created_at ? '' : util.toDateString(d.created_at) } },
{ field: 'updated_at', title: '更新时间', align: 'center', width: 160, templet: function (d) { return !d.updated_at ? '' : util.toDateString(d.updated_at) } },
{ title: '操作', toolbar: '#actionBox', align: 'center', width: 300, fixed: 'right' },
]],
skin: 'grid',
toolbar: '#toolbar',
defaultToolbar: [{
title: '刷新',
layEvent: 'refresh',
icon: 'layui-icon-refresh',
}, 'filter', 'exports'],
request: {
pageName: 'page',
limitName: 'rows'
}
});
// 工具栏
table.on('toolbar(tablelist)', function (obj) {
switch (obj.event) {
case 'add': add(); break;
case 'refresh': table.reload('tablelist'); break;
case 'refresh_cache': refreshCache(); break;
case 'rebuild_parent_path': rebuildParentPath(); break;
case 'search': search(); break;
}
});
function add(obj) {
layer.open({
type: 2,
title: '新增',
shade: 0.1,
area: ['99%', '98%'],
content: "/system/role/add"
});
}
function refreshCache() {
layer.confirm('确定要刷新角色数据吗?', { title: '提示' }, function (index, layero) {
$.ajax({
url: '/system/role/refresh_cache',
type: 'post',
headers: { 'X-CSRF-Token': '{{ token }}' },
dataType: 'json',
success: function (result) {
if (result.success) {
layer.msg(result.msg, { icon: 1, time: 2000 });
search_btn();
} else {
layer.msg(result.msg, { icon: 2 })
}
}
});
layer.close(index); // 关闭弹窗
}, function (index, layero) {
layer.close(index); // 关闭弹窗
});
}
function rebuildParentPath() {
layer.confirm('确定要重建父路径吗?', { title: '提示' }, function (index, layero) {
$.ajax({
url: '/system/role/rebuild_parent_path',
type: 'post',
headers: { 'X-CSRF-Token': '{{ token }}' },
dataType: 'json',
success: function (result) {
if (result.success) {
layer.msg(result.msg, { icon: 1, time: 2000 });
search_btn();
} else {
layer.msg(result.msg, { icon: 2 })
}
}
});
layer.close(index); // 关闭弹窗
}, function (index, layero) {
layer.close(index); // 关闭弹窗
});
}
function search() {
layer.open({
type: 1,
offset: '20px',
title: '搜索',
content: $('#search-layer'), // 捕获的元素
shade: 0.1,
shadeClose: false,
scrollbar: false,
resize: false,
move: false,
skin: 'search-layer-open',
area: ['50%', '350px'],
btn: ['搜索', '重置'],
btn1: function (index, layero) {
search_btn();
layer.close(index);
},
btn2: function (index, layero) {
$('#status').val(0);
$('#id').val('');
$('#name').val('');
$('#parentId').val(0);
form.render('select');
return false;
}
});
}
// 表格项操作按钮
table.on('tool(tablelist)', function (obj) {
switch (obj.event) {
case 'edit': edit(obj); break;
case 'set_menu': setMenu(obj); break;
case 'refresh_role_menus': refreshRoleMenus(obj); break;
}
});
function edit(obj) {
layer.open({
type: 2,
title: '修改',
shade: 0.1,
area: ['99%', '98%'],
content: "/system/role/edit?id=" + obj.data['id']
});
}
function setMenu(obj) {
layer.open({
type: 2,
title: '为 ' + obj.data['display_name'] + ' 分配权限',
shade: 0.1,
area: ['99%', '98%'],
content: "/system/role/set_menu?id=" + obj.data['id']
});
}
function refreshRoleMenus(obj) {
layer.confirm('确定要刷新角色权限吗?', { title: '提示' }, function (index, layero) {
$.ajax({
url: '/system/role/refresh_role_menus',
type: 'post',
headers: { 'X-CSRF-Token': '{{ token }}' },
dataType: 'json',
data: { "roleID": obj.data['id'] },
success: function (result) {
if (result.success) {
layer.msg(result.msg, { icon: 1, time: 2000 });
} else {
layer.msg(result.msg, { icon: 2 })
}
}
});
layer.close(index); // 关闭弹窗
}, function (index, layero) {
layer.close(index); // 关闭弹窗
});
}
// 搜索条件
function getQueryParams() {
return {
status: $('#status').val(),
name: $('#name').val(),
id: $('#id').val(),
parentId: $('#parentId').val()
};
}
// 搜索
function search_btn() {
table.reload('tablelist', {
where: getQueryParams(),
page: {
curr: 1
}
})
}
function getRoleTree() {
$.ajax({
url: "/system/role/data?type=tree",
type: 'post',
dataType: 'json',
headers: { 'X-CSRF-Token': '{{ token }}' },
success: function (res) {
tree.render({
elem: '#roleTree',
data: res,
onlyIconControl: true, // 是否仅允许节点左侧图标控制展开收缩
showLine: true,
click: function (obj) {
// console.log(obj.data); // 得到当前点击的节点数据
// console.log(obj.state); // 得到当前节点的展开状态open、close、normal
// console.log(obj.elem); // 得到当前节点元素
// console.log(obj.data.children); // 当前节点下是否有子节点
$('#parentId').val(obj.data.id);
search_btn();
}
});
},
error: function (err) {
// 处理请求错误
console.log('请求出错:', err);
}
});
}
});
</script>
}

View File

@@ -0,0 +1,194 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898
package role
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"context"
"management/internal/erpserver/templ/base"
"management/internal/erpserver/templ/component"
"management/internal/pkg/mid"
)
func List(ctx context.Context) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
meuns := mid.GetCurMenus(ctx)
templ_7745c5c3_Err = component.TemplBtn(meuns, true, "add", "refresh_cache", "rebuild_parent_path").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = component.TemplLink(meuns, "edit", "set_menu", "refresh_role_menus").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " <div class=\"search-layer\" id=\"search-layer\" style=\"display: none;\"><input type=\"hidden\" id=\"parentId\" name=\"parentId\" value=\"0\"><div class=\"layui-form layui-row\"><div class=\"layui-col-xs12 layui-col-sm12 layui-col-md4\"><div class=\"layui-form-column\"><label for=\"status\" class=\"tips\">状态</label> <select id=\"status\"><option value=\"0\">正常</option> <option value=\"-1\">删除</option> <option value=\"9999\">全部</option></select></div></div><div class=\"layui-col-xs12 layui-col-sm12 layui-col-md4\"><div class=\"layui-form-column\"><label for=\"id\" class=\"tips\">编号</label> <input type=\"text\" id=\"id\" placeholder=\"请输入编号\" class=\"layui-input\"></div></div><div class=\"layui-col-xs12 layui-col-sm12 layui-col-md4\"><div class=\"layui-form-column\"><label for=\"name\" class=\"tips\">名称</label> <input type=\"text\" id=\"name\" placeholder=\"请输入名称\" class=\"layui-input\"></div></div></div></div><div class=\"layui-row h-all\"><div class=\"layui-col-md2 h-all\"><div class=\"own-left-pannel\"><div id=\"roleTree\" class=\"own-tree\"></div></div></div><div class=\"layui-col-md10\"><div class=\"own-pannel\"><table id=\"tablelist\" lay-filter=\"tablelist\"></table></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = base.Base(ctx, listCss(), listJs(ctx)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func listCss() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
return nil
})
}
func listJs(ctx context.Context) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
token := mid.GetCsrfToken(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script>\n layui.use(['jquery', 'table', 'form', 'tree', 'util'], function () {\n let $ = layui.jquery;\n let table = layui.table;\n let form = layui.form;\n let tree = layui.tree;\n let util = layui.util;\n\n getRoleTree();\n\n // 加载列表\n table.render({\n elem: '#tablelist',\n url: \"/system/role/list\",\n method: \"POST\",\n headers: { 'X-CSRF-Token': '")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var5, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 80, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "' },\n where: getQueryParams(),\n height: function () {\n return $(window).height() - 22;\n },\n page: true,\n limit: 15,\n limits: [15, 30, 45, 60, 75, 90],\n cols: [[\n { field: 'display_name', title: '显示名称', align: 'left', width: 160, fixed: 'left' },\n { field: 'name', title: '名称', align: 'left', width: 150, fixed: 'left' },\n {\n field: 'status', title: '状态', align: 'center', width: 90, templet: function (row) {\n switch (row.status) {\n case 0: return '正常';\n case -1: return '删除';\n default: return '其它';\n }\n return '';\n }\n },\n { field: 'sort', title: '排序', align: 'center', width: 60 },\n { field: 'created_at', title: '创建时间', align: 'center', width: 160, templet: function (d) { return !d.created_at ? '' : util.toDateString(d.created_at) } },\n { field: 'updated_at', title: '更新时间', align: 'center', width: 160, templet: function (d) { return !d.updated_at ? '' : util.toDateString(d.updated_at) } },\n { title: '操作', toolbar: '#actionBox', align: 'center', width: 300, fixed: 'right' },\n ]],\n skin: 'grid',\n toolbar: '#toolbar',\n defaultToolbar: [{\n title: '刷新',\n layEvent: 'refresh',\n icon: 'layui-icon-refresh',\n }, 'filter', 'exports'],\n request: {\n pageName: 'page',\n limitName: 'rows'\n }\n });\n\n // 工具栏\n table.on('toolbar(tablelist)', function (obj) {\n switch (obj.event) {\n case 'add': add(); break;\n case 'refresh': table.reload('tablelist'); break;\n case 'refresh_cache': refreshCache(); break;\n case 'rebuild_parent_path': rebuildParentPath(); break;\n case 'search': search(); break;\n }\n });\n\n function add(obj) {\n layer.open({\n type: 2,\n title: '新增',\n shade: 0.1,\n area: ['99%', '98%'],\n content: \"/system/role/add\"\n });\n }\n\n function refreshCache() {\n layer.confirm('确定要刷新角色数据吗?', { title: '提示' }, function (index, layero) {\n $.ajax({\n url: '/system/role/refresh_cache',\n type: 'post',\n headers: { 'X-CSRF-Token': '")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var6, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 145, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "' },\n dataType: 'json',\n success: function (result) {\n if (result.success) {\n layer.msg(result.msg, { icon: 1, time: 2000 });\n search_btn();\n } else {\n layer.msg(result.msg, { icon: 2 })\n }\n }\n });\n layer.close(index); // 关闭弹窗\n }, function (index, layero) {\n layer.close(index); // 关闭弹窗\n });\n }\n\n function rebuildParentPath() {\n layer.confirm('确定要重建父路径吗?', { title: '提示' }, function (index, layero) {\n $.ajax({\n url: '/system/role/rebuild_parent_path',\n type: 'post',\n headers: { 'X-CSRF-Token': '")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var7, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 167, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "' },\n dataType: 'json',\n success: function (result) {\n if (result.success) {\n layer.msg(result.msg, { icon: 1, time: 2000 });\n search_btn();\n } else {\n layer.msg(result.msg, { icon: 2 })\n }\n }\n });\n layer.close(index); // 关闭弹窗\n }, function (index, layero) {\n layer.close(index); // 关闭弹窗\n });\n }\n\n function search() {\n layer.open({\n type: 1,\n offset: '20px',\n title: '搜索',\n content: $('#search-layer'), // 捕获的元素\n shade: 0.1,\n shadeClose: false,\n scrollbar: false,\n resize: false,\n move: false,\n skin: 'search-layer-open',\n area: ['50%', '350px'],\n btn: ['搜索', '重置'],\n btn1: function (index, layero) {\n search_btn();\n layer.close(index);\n },\n btn2: function (index, layero) {\n $('#status').val(0);\n $('#id').val('');\n $('#name').val('');\n $('#parentId').val(0);\n form.render('select');\n return false;\n }\n });\n }\n\n // 表格项操作按钮\n table.on('tool(tablelist)', function (obj) {\n switch (obj.event) {\n case 'edit': edit(obj); break;\n case 'set_menu': setMenu(obj); break;\n case 'refresh_role_menus': refreshRoleMenus(obj); break;\n }\n });\n\n function edit(obj) {\n layer.open({\n type: 2,\n title: '修改',\n shade: 0.1,\n area: ['99%', '98%'],\n content: \"/system/role/edit?id=\" + obj.data['id']\n });\n }\n\n function setMenu(obj) {\n layer.open({\n type: 2,\n title: '为 ' + obj.data['display_name'] + ' 分配权限',\n shade: 0.1,\n area: ['99%', '98%'],\n content: \"/system/role/set_menu?id=\" + obj.data['id']\n });\n }\n\n function refreshRoleMenus(obj) {\n layer.confirm('确定要刷新角色权限吗?', { title: '提示' }, function (index, layero) {\n $.ajax({\n url: '/system/role/refresh_role_menus',\n type: 'post',\n headers: { 'X-CSRF-Token': '")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var8, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 247, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "' },\n dataType: 'json',\n data: { \"roleID\": obj.data['id'] },\n success: function (result) {\n if (result.success) {\n layer.msg(result.msg, { icon: 1, time: 2000 });\n } else {\n layer.msg(result.msg, { icon: 2 })\n }\n }\n });\n layer.close(index); // 关闭弹窗\n }, function (index, layero) {\n layer.close(index); // 关闭弹窗\n });\n }\n\n // 搜索条件\n function getQueryParams() {\n return {\n status: $('#status').val(),\n name: $('#name').val(),\n id: $('#id').val(),\n parentId: $('#parentId').val()\n };\n }\n\n // 搜索\n function search_btn() {\n table.reload('tablelist', {\n where: getQueryParams(),\n page: {\n curr: 1\n }\n })\n }\n\n function getRoleTree() {\n $.ajax({\n url: \"/system/role/data?type=tree\",\n type: 'post',\n dataType: 'json',\n headers: { 'X-CSRF-Token': '")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var9, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 289, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "' },\n success: function (res) {\n tree.render({\n elem: '#roleTree',\n data: res,\n onlyIconControl: true, // 是否仅允许节点左侧图标控制展开收缩\n showLine: true,\n click: function (obj) {\n // console.log(obj.data); // 得到当前点击的节点数据\n // console.log(obj.state); // 得到当前节点的展开状态open、close、normal\n // console.log(obj.elem); // 得到当前节点元素\n // console.log(obj.data.children); // 当前节点下是否有子节点\n $('#parentId').val(obj.data.id);\n search_btn();\n }\n });\n },\n error: function (err) {\n // 处理请求错误\n console.log('请求出错:', err);\n }\n });\n }\n });\n </script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -12,8 +12,8 @@ import (
"management/internal/erpserver/service/v1"
commonService "management/internal/erpserver/service/v1/common"
systemService "management/internal/erpserver/service/v1/system"
"management/internal/pkg/cache"
"management/internal/pkg/config"
"management/internal/pkg/redis"
"management/internal/pkg/render"
"management/internal/pkg/session"
@@ -70,7 +70,8 @@ var serverSet = wire.NewSet(
func NewWire(*config.Config, *logger.Logger) (App, func(), error) {
panic(wire.Build(
repositorySet,
redis.New,
cache.ConnectRedis,
cache.NewRedisCache,
session.NewSCSManager,
serviceSet,
render.New,

View File

@@ -17,8 +17,8 @@ import (
"management/internal/erpserver/service/v1"
"management/internal/erpserver/service/v1/common"
system2 "management/internal/erpserver/service/v1/system"
"management/internal/pkg/cache"
"management/internal/pkg/config"
"management/internal/pkg/redis"
"management/internal/pkg/render"
"management/internal/pkg/session"
)
@@ -39,22 +39,24 @@ func NewWire(configConfig *config.Config, loggerLogger *logger.Logger) (App, fun
configRepository := system.NewConfigRepository(repositoryRepository)
loginLogRepository := system.NewLoginLogRepository(repositoryRepository)
auditLogRepository := system.NewAuditLogRepository(repositoryRepository)
manager, err := session.NewSCSManager(db, configConfig)
if err != nil {
cleanup()
return App{}, nil, err
}
transaction := repository.NewTransaction(repositoryRepository)
cache, cleanup2, err := redis.New(configConfig, loggerLogger)
client, cleanup2, err := cache.ConnectRedis(configConfig, loggerLogger)
if err != nil {
cleanup()
return App{}, nil, err
}
service := v1.NewService(loggerLogger, transaction, manager, cache)
manager, err := session.NewSCSManager(client, configConfig)
if err != nil {
cleanup2()
cleanup()
return App{}, nil, err
}
cacheCache := cache.NewRedisCache(client)
service := v1.NewService(loggerLogger, transaction, manager, cacheCache)
auditLogService := system2.NewAuditLogService(service, auditLogRepository)
roleService := system2.NewRoleService(service, roleRepository)
roleMenuService := system2.NewRoleMenuService(service, roleMenuRepository)
menuService := system2.NewMenuService(service, menuRepository, roleService, roleMenuService)
auditLogService := system2.NewAuditLogService(service, auditLogRepository)
renderRender, err := render.New(manager, menuService)
if err != nil {
cleanup2()
@@ -78,7 +80,7 @@ func NewWire(configConfig *config.Config, loggerLogger *logger.Logger) (App, fun
roleHandler := system3.NewRoleHandler(handlerHandler, roleService, menuService)
departmentHandler := system3.NewDepartmentHandler(handlerHandler, departmentService)
mux := NewHTTPServer(manager, loggerLogger, menuService, auditLogService, captchaHandler, uploadHandler, configHandler, homeHandler, userHandler, loginLogHandler, auditHandler, menuHandler, roleHandler, departmentHandler)
app := NewApp(userRepository, roleRepository, menuRepository, roleMenuRepository, departmentRepository, configRepository, loginLogRepository, auditLogRepository, mux)
app := NewApp(userRepository, roleRepository, menuRepository, roleMenuRepository, departmentRepository, configRepository, loginLogRepository, auditLogRepository, auditLogService, mux)
return app, func() {
cleanup2()
cleanup()

19
internal/pkg/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,19 @@
package cache
import (
"context"
"time"
"github.com/redis/go-redis/v9"
)
type Cache 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)
}

View File

@@ -1,4 +1,4 @@
package redis
package cache
import (
"bytes"
@@ -16,28 +16,17 @@ import (
var ErrRedisKeyNotFound = errors.New("redis key not found")
type Cache 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)
}
type redisCache struct {
client *redis.Client
}
func New(conf *config.Config, log *logger.Logger) (Cache, func(), error) {
func ConnectRedis(conf *config.Config, log *logger.Logger) (*redis.Client, func(), error) {
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", conf.Redis.Host, conf.Redis.Port),
Password: conf.Redis.Password,
DB: conf.Redis.DB,
})
_, err := rdb.Ping(context.Background()).Result()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
_, err := rdb.Ping(ctx).Result()
if err != nil {
return nil, nil, err
}
@@ -48,9 +37,17 @@ func New(conf *config.Config, log *logger.Logger) (Cache, func(), error) {
}
}
return rdb, cleanup, nil
}
type redisCache struct {
client *redis.Client
}
func NewRedisCache(client *redis.Client) Cache {
return &redisCache{
client: rdb,
}, cleanup, nil
client: client,
}
}
func (r *redisCache) Encode(a any) ([]byte, error) {

View File

@@ -0,0 +1,58 @@
package mid
//import (
// "context"
// "errors"
// "net/http"
// "time"
//
// systemmodel "management/internal/erpserver/model/system"
// v1 "management/internal/erpserver/service/v1"
// "management/internal/pkg/know"
// "management/internal/pkg/session"
//
// "github.com/drhin/logger"
// "go.uber.org/zap"
//)
//
//func Audit(sess session.Manager, auditLogService v1.AuditLogService, log *logger.Logger) func(http.Handler) http.Handler {
// return func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// start := time.Now()
//
// // 提前获取用户信息(同步操作)
// user, err := sess.GetUser(r.Context(), know.StoreName)
// if err != nil {
// log.Error("获取用户会话失败", err)
// next.ServeHTTP(w, r) // 继续处理请求
// return
// }
//
// defer func() {
// go func() {
// if user.ID == 0 {
// log.Error("用户信息为空", errors.New("scs get user is empty"))
// return
// }
//
// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
// defer cancel()
//
// al := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
// if err := auditLogService.Create(ctx, al); err != nil {
// log.Error(err.Error(), err,
// zap.Int32("user_id", user.ID),
// zap.String("user", user.Email),
// zap.String("ip", al.Ip),
// zap.String("os", al.Os),
// zap.String("method", al.Method),
// zap.String("path", al.Url),
// )
// }
// }()
// }()
//
// next.ServeHTTP(w, r)
// })
// }
//}

View File

@@ -0,0 +1,228 @@
package mid
import (
"context"
"errors"
"net/http"
"sync"
"time"
systemmodel "management/internal/erpserver/model/system"
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/know"
"management/internal/pkg/session"
"github.com/drhin/logger"
"go.uber.org/zap"
)
// AuditBuffer 审计日志缓冲器
type AuditBuffer struct {
auditLogService v1.AuditLogService
log *logger.Logger
buffer chan *systemmodel.AuditLog
stopCh chan struct{}
wg sync.WaitGroup
batchSize int
flushInterval time.Duration
}
// NewAuditBuffer 创建审计日志缓冲器
func NewAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) *AuditBuffer {
return &AuditBuffer{
auditLogService: auditLogService,
log: log,
buffer: make(chan *systemmodel.AuditLog, 10000), // 缓冲区大小
stopCh: make(chan struct{}),
batchSize: 50, // 批量大小
flushInterval: 3 * time.Second, // 刷新间隔
}
}
// Start 启动缓冲器
func (ab *AuditBuffer) Start() {
ab.wg.Add(1)
go ab.processBuffer()
}
// Stop 停止缓冲器
func (ab *AuditBuffer) Stop() {
close(ab.stopCh)
ab.wg.Wait()
close(ab.buffer)
}
// Add 添加审计日志到缓冲区
func (ab *AuditBuffer) Add(auditLog *systemmodel.AuditLog) {
select {
case ab.buffer <- auditLog:
// 成功添加到缓冲区
default:
// 缓冲区满,记录警告但不阻塞
ab.log.Warn("审计日志缓冲区已满,丢弃日志")
}
}
// processBuffer 处理缓冲区中的日志
func (ab *AuditBuffer) processBuffer() {
defer ab.wg.Done()
ticker := time.NewTicker(ab.flushInterval)
defer ticker.Stop()
batch := make([]*systemmodel.AuditLog, 0, ab.batchSize)
flushBatch := func() {
if len(batch) == 0 {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 批量插入
if err := ab.batchInsert(ctx, batch); err != nil {
ab.log.Error("批量插入审计日志失败", err, zap.Int("count", len(batch)))
} else {
ab.log.Debug("批量插入审计日志成功", zap.Int("count", len(batch)))
}
// 清空批次
batch = batch[:0]
}
for {
select {
case <-ab.stopCh:
// 停止信号,处理剩余的日志
for len(ab.buffer) > 0 {
select {
case auditLog := <-ab.buffer:
batch = append(batch, auditLog)
if len(batch) >= ab.batchSize {
flushBatch()
}
default:
break
}
}
flushBatch() // 处理最后一批
return
case <-ticker.C:
// 定时刷新
flushBatch()
case auditLog := <-ab.buffer:
// 收到新的审计日志
batch = append(batch, auditLog)
if len(batch) >= ab.batchSize {
flushBatch()
}
}
}
}
// batchInsert 批量插入数据库
func (ab *AuditBuffer) batchInsert(ctx context.Context, auditLogs []*systemmodel.AuditLog) error {
maxRetries := 3
for i := 0; i < maxRetries; i++ {
// 假设你的服务有批量创建方法,如果没有,需要添加
if err := ab.auditLogService.BatchCreate(ctx, auditLogs); err != nil {
if i == maxRetries-1 {
return err
}
ab.log.Error("批量插入失败,准备重试", err, zap.Int("retry", i+1))
time.Sleep(time.Duration(i+1) * time.Second)
continue
}
return nil
}
return nil
}
// 全局缓冲器实例
var globalAuditBuffer *AuditBuffer
// InitAuditBuffer 初始化全局缓冲器
func InitAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) {
globalAuditBuffer = NewAuditBuffer(auditLogService, log)
globalAuditBuffer.Start()
}
// StopAuditBuffer 停止全局缓冲器
func StopAuditBuffer() {
if globalAuditBuffer != nil {
globalAuditBuffer.Stop()
}
}
// Audit 优化后的中间件
func Audit(sess session.Manager, log *logger.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 提前获取用户信息
user, err := sess.GetUser(r.Context(), know.StoreName)
if err != nil {
log.Error("获取用户会话失败", err)
next.ServeHTTP(w, r)
return
}
// 处理请求
next.ServeHTTP(w, r)
// 异步添加到缓冲区
go func() {
if user.ID == 0 {
log.Error("用户信息为空", errors.New("user is empty"))
return
}
auditLog := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
// 添加到缓冲区,不会阻塞
if globalAuditBuffer != nil {
globalAuditBuffer.Add(auditLog)
}
}()
})
}
}
// 如果你的AuditLogService没有BatchCreate方法需要添加这个接口
// 在你的service接口中添加
/*
type AuditLogService interface {
Create(ctx context.Context, auditLog *systemmodel.AuditLog) error
BatchCreate(ctx context.Context, auditLogs []*systemmodel.AuditLog) error
// ... 其他方法
}
*/
// 以及对应的实现PostgreSQL批量插入示例
/*
func (s *auditLogService) BatchCreate(ctx context.Context, auditLogs []*systemmodel.AuditLog) error {
if len(auditLogs) == 0 {
return nil
}
// 构建批量插入SQL
query := `INSERT INTO audit_logs (user_id, email, ip, os, browser, method, url, start_time, end_time, duration) VALUES `
values := make([]interface{}, 0, len(auditLogs)*10)
for i, log := range auditLogs {
if i > 0 {
query += ", "
}
query += "($" + strconv.Itoa(i*10+1) + ", $" + strconv.Itoa(i*10+2) + ", $" + strconv.Itoa(i*10+3) + ", $" + strconv.Itoa(i*10+4) + ", $" + strconv.Itoa(i*10+5) + ", $" + strconv.Itoa(i*10+6) + ", $" + strconv.Itoa(i*10+7) + ", $" + strconv.Itoa(i*10+8) + ", $" + strconv.Itoa(i*10+9) + ", $" + strconv.Itoa(i*10+10) + ")"
values = append(values, log.UserID, log.Email, log.Ip, log.Os, log.Browser, log.Method, log.Url, log.StartTime, log.EndTime, log.Duration)
}
_, err := s.db.ExecContext(ctx, query, values...)
return err
}
*/

View File

@@ -0,0 +1,99 @@
package mid
//import (
// "log"
// "net/http"
// "time"
//
// "management/internal/erpserver/model/dto"
// v1 "management/internal/erpserver/service/v1"
// "management/internal/pkg/know"
// "management/internal/pkg/session"
//)
//
//var publicRoutes = map[string]bool{
// "/home.html": true,
// "/dashboard": true,
// "/system/menus": true,
// "/upload/img": true,
// "/upload/file": true,
// "/upload/multi_files": true,
// "/pear.json": true,
// "/logout": true,
//}
//
//func Authorize(
// sess session.Manager,
// menuService v1.MenuService,
//) func(http.Handler) http.Handler {
// return func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// path := r.URL.Path
//
// // 登陆检查
// n := time.Now()
// user, err := sess.GetUser(ctx, know.StoreName)
// if err != nil || user.ID == 0 {
// http.Redirect(w, r, "/", http.StatusFound)
// return
// }
//
// log.Printf("scs get user: %s", time.Since(n).String())
//
// // 公共路由放行
// if publicRoutes[path] {
// ctx = setUser(ctx, user)
// next.ServeHTTP(w, r.WithContext(ctx))
// return
// }
//
// n1 := time.Now()
// // 权限检查
// menus, err := menuService.ListByRoleIDToMap(ctx, user.RoleID)
// if err != nil || !hasPermission(menus, path) {
// http.Error(w, "Forbidden", http.StatusForbidden)
// return
// }
//
// log.Printf("listByRoleIDToMap: %s", time.Since(n1).String())
//
// n2 := time.Now()
// cur := getCurrentMenus(menus, path)
// log.Printf("getCurrentMenus: %s", time.Since(n2).String())
//
// ctx = setUser(ctx, user)
// ctx = setCurMenus(ctx, cur)
//
// next.ServeHTTP(w, r.WithContext(ctx))
// })
// }
//}
//
//func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
// _, ok := menus[path]
// return ok
//}
//
//func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
// var res []dto.OwnerMenuDto
//
// menu, ok := data[path]
// if !ok {
// return res
// }
//
// for _, item := range data {
// 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
//}

View File

@@ -0,0 +1,402 @@
package mid
//
//import (
// "context"
// "log"
// "net/http"
// "sync"
// "time"
//
// "management/internal/erpserver/model/dto"
// v1 "management/internal/erpserver/service/v1"
// "management/internal/pkg/know"
// "management/internal/pkg/session"
//)
//
//var publicRoutes = map[string]bool{
// "/home.html": true,
// "/dashboard": true,
// "/system/menus": true,
// "/upload/img": true,
// "/upload/file": true,
// "/upload/multi_files": true,
// "/pear.json": true,
// "/logout": true,
//}
//
//// MenuCacheItem 菜单缓存项
//type MenuCacheItem struct {
// Data map[string]*dto.OwnerMenuDto
// ExpireAt time.Time
// LoadTime time.Time
// Version int64
//}
//
//// IsExpired 检查是否过期
//func (item *MenuCacheItem) IsExpired() bool {
// return time.Now().After(item.ExpireAt)
//}
//
//// MenuCache 内存缓存管理器
//type MenuCache struct {
// mu sync.RWMutex
// cache map[int32]*MenuCacheItem // roleID -> MenuCacheItem
// maxSize int // 最大缓存条目数
// ttl time.Duration // 缓存TTL
// refreshTTL time.Duration // 刷新TTL (提前刷新时间)
// stats CacheStats // 缓存统计
// cleanupTick *time.Ticker // 清理定时器
// stopCh chan struct{} // 停止信号
//}
//
//// CacheStats 缓存统计信息
//type CacheStats struct {
// mu sync.RWMutex
// Hits int64
// Misses int64
// Evictions int64
// RefreshCount int64
//}
//
//// NewMenuCache 创建新的菜单缓存
//func NewMenuCache(maxSize int, ttl time.Duration) *MenuCache {
// cache := &MenuCache{
// cache: make(map[int32]*MenuCacheItem),
// maxSize: maxSize,
// ttl: ttl,
// refreshTTL: ttl - time.Duration(float64(ttl)*0.1), // 提前10%刷新
// stopCh: make(chan struct{}),
// cleanupTick: time.NewTicker(time.Minute * 5), // 每5分钟清理一次
// }
//
// // 启动后台清理协程
// go cache.cleanup()
//
// return cache
//}
//
//// Get 获取缓存数据
//func (mc *MenuCache) Get(roleID int32) (map[string]*dto.OwnerMenuDto, bool) {
// mc.mu.RLock()
// item, exists := mc.cache[roleID]
// mc.mu.RUnlock()
//
// if !exists {
// mc.recordMiss()
// return nil, false
// }
//
// // 检查是否过期
// if item.IsExpired() {
// mc.recordMiss()
// // 异步删除过期项
// go func() {
// mc.mu.Lock()
// delete(mc.cache, roleID)
// mc.mu.Unlock()
// }()
// return nil, false
// }
//
// mc.recordHit()
// return item.Data, true
//}
//
//// Set 设置缓存数据
//func (mc *MenuCache) Set(roleID int32, data map[string]*dto.OwnerMenuDto, version int64) {
// mc.mu.Lock()
// defer mc.mu.Unlock()
//
// // 如果缓存已满执行LRU淘汰
// if len(mc.cache) >= mc.maxSize {
// mc.evictLRU()
// }
//
// item := &MenuCacheItem{
// Data: data,
// ExpireAt: time.Now().Add(mc.ttl),
// LoadTime: time.Now(),
// Version: version,
// }
//
// mc.cache[roleID] = item
//}
//
//// NeedRefresh 检查是否需要提前刷新
//func (mc *MenuCache) NeedRefresh(roleID int32) bool {
// mc.mu.RLock()
// item, exists := mc.cache[roleID]
// mc.mu.RUnlock()
//
// if !exists {
// return true
// }
//
// // 提前刷新策略在过期前10%时间开始刷新
// refreshTime := item.LoadTime.Add(mc.refreshTTL)
// return time.Now().After(refreshTime)
//}
//
//// evictLRU 淘汰最久未使用的缓存项
//func (mc *MenuCache) evictLRU() {
// var oldestRoleID int32
// var oldestTime time.Time = time.Now()
//
// for roleID, item := range mc.cache {
// if item.LoadTime.Before(oldestTime) {
// oldestTime = item.LoadTime
// oldestRoleID = roleID
// }
// }
//
// if oldestRoleID != 0 {
// delete(mc.cache, oldestRoleID)
// mc.stats.mu.Lock()
// mc.stats.Evictions++
// mc.stats.mu.Unlock()
// }
//}
//
//// cleanup 后台清理过期缓存
//func (mc *MenuCache) cleanup() {
// for {
// select {
// case <-mc.cleanupTick.C:
// mc.cleanupExpired()
// case <-mc.stopCh:
// mc.cleanupTick.Stop()
// return
// }
// }
//}
//
//// cleanupExpired 清理过期缓存
//func (mc *MenuCache) cleanupExpired() {
// mc.mu.Lock()
// defer mc.mu.Unlock()
//
// now := time.Now()
// for roleID, item := range mc.cache {
// if now.After(item.ExpireAt) {
// delete(mc.cache, roleID)
// }
// }
//}
//
//// GetStats 获取缓存统计信息
//func (mc *MenuCache) GetStats() CacheStats {
// mc.stats.mu.RLock()
// defer mc.stats.mu.RUnlock()
// return mc.stats
//}
//
//// recordHit 记录缓存命中
//func (mc *MenuCache) recordHit() {
// mc.stats.mu.Lock()
// mc.stats.Hits++
// mc.stats.mu.Unlock()
//}
//
//// recordMiss 记录缓存未命中
//func (mc *MenuCache) recordMiss() {
// mc.stats.mu.Lock()
// mc.stats.Misses++
// mc.stats.mu.Unlock()
//}
//
//// Close 关闭缓存
//func (mc *MenuCache) Close() {
// close(mc.stopCh)
//}
//
//// CachedMenuService 带缓存的菜单服务包装器
//type CachedMenuService struct {
// menuService v1.MenuService
// cache *MenuCache
// mu sync.RWMutex
// refreshing map[int32]bool // 正在刷新的roleID
//}
//
//// NewCachedMenuService 创建带缓存的菜单服务
//func NewCachedMenuService(menuService v1.MenuService, cache *MenuCache) *CachedMenuService {
// return &CachedMenuService{
// menuService: menuService,
// cache: cache,
// refreshing: make(map[int32]bool),
// }
//}
//
//// ListByRoleIDToMap 获取菜单数据(带缓存)
//func (cms *CachedMenuService) ListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
// // 先尝试从缓存获取
// if data, hit := cms.cache.Get(roleID); hit {
// // 检查是否需要异步刷新
// if cms.cache.NeedRefresh(roleID) {
// go cms.asyncRefresh(ctx, roleID)
// }
// return data, nil
// }
//
// // 缓存未命中,同步获取数据
// return cms.loadAndCache(ctx, roleID)
//}
//
//// loadAndCache 加载数据并缓存
//func (cms *CachedMenuService) loadAndCache(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
// // 防止并发重复加载
// cms.mu.Lock()
// if cms.refreshing[roleID] {
// cms.mu.Unlock()
// // 如果正在加载,等待一小段时间后重试缓存
// time.Sleep(time.Millisecond * 10)
// if data, hit := cms.cache.Get(roleID); hit {
// return data, nil
// }
// // 重试失败,继续执行加载逻辑
// cms.mu.Lock()
// }
// cms.refreshing[roleID] = true
// cms.mu.Unlock()
//
// defer func() {
// cms.mu.Lock()
// delete(cms.refreshing, roleID)
// cms.mu.Unlock()
// }()
//
// // 从原始服务获取数据
// data, err := cms.menuService.ListByRoleIDToMap(ctx, roleID)
// if err != nil {
// return nil, err
// }
//
// // 缓存数据
// version := time.Now().UnixNano()
// cms.cache.Set(roleID, data, version)
//
// return data, nil
//}
//
//// asyncRefresh 异步刷新缓存
//func (cms *CachedMenuService) asyncRefresh(ctx context.Context, roleID int32) {
// // 使用背景上下文,避免原请求取消影响刷新
// bgCtx := context.Background()
//
// cms.mu.RLock()
// if cms.refreshing[roleID] {
// cms.mu.RUnlock()
// return
// }
// cms.mu.RUnlock()
//
// _, err := cms.loadAndCache(bgCtx, roleID)
// if err != nil {
// log.Printf("async refresh menu cache failed for roleID %d: %v", roleID, err)
// }
//
// cms.cache.stats.mu.Lock()
// cms.cache.stats.RefreshCount++
// cms.cache.stats.mu.Unlock()
//}
//
//// 全局缓存实例
//var (
// menuCache *MenuCache
// cachedMenuSvc *CachedMenuService
// cacheInitOnce sync.Once
//)
//
//// InitMenuCache 初始化菜单缓存(在应用启动时调用)
//func InitMenuCache(menuService v1.MenuService) {
// cacheInitOnce.Do(func() {
// // 配置参数最大1000个角色的缓存TTL 10分钟
// menuCache = NewMenuCache(1000, time.Minute*10)
// cachedMenuSvc = NewCachedMenuService(menuService, menuCache)
// })
//}
//
//// GetCachedMenuService 获取缓存菜单服务实例
//func GetCachedMenuService() *CachedMenuService {
// return cachedMenuSvc
//}
//
//// Authorize 修改后的授权中间件
//func Authorize(
// sess session.Manager,
// menuService v1.MenuService,
//) func(http.Handler) http.Handler {
// // 初始化缓存
// InitMenuCache(menuService)
//
// return func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// path := r.URL.Path
//
// // 登陆检查
// n := time.Now()
// user, err := sess.GetUser(ctx, know.StoreName)
// if err != nil || user.ID == 0 {
// http.Redirect(w, r, "/", http.StatusFound)
// return
// }
//
// log.Printf("scs get user: %s", time.Since(n).String())
//
// // 公共路由放行
// if publicRoutes[path] {
// ctx = setUser(ctx, user)
// next.ServeHTTP(w, r.WithContext(ctx))
// return
// }
//
// n1 := time.Now()
// // 权限检查 - 使用缓存服务
// menus, err := GetCachedMenuService().ListByRoleIDToMap(ctx, user.RoleID)
// if err != nil || !hasPermission(menus, path) {
// http.Error(w, "Forbidden", http.StatusForbidden)
// return
// }
//
// log.Printf("listByRoleIDToMap (cached): %s", time.Since(n1).String())
//
// n2 := time.Now()
// cur := getCurrentMenus(menus, path)
// log.Printf("getCurrentMenus: %s", time.Since(n2).String())
//
// ctx = setUser(ctx, user)
// ctx = setCurMenus(ctx, cur)
//
// next.ServeHTTP(w, r.WithContext(ctx))
// })
// }
//}
//
//func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
// _, ok := menus[path]
// return ok
//}
//
//func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
// var res []dto.OwnerMenuDto
//
// menu, ok := data[path]
// if !ok {
// return res
// }
//
// for _, item := range data {
// 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
//}

View File

@@ -0,0 +1,537 @@
package mid
//import (
// "context"
// "encoding/json"
// "errors"
// "fmt"
// "log"
// "net/http"
// "strconv"
// "sync"
// "time"
//
// "management/internal/erpserver/model/dto"
// v1 "management/internal/erpserver/service/v1"
// "management/internal/pkg/know"
// "management/internal/pkg/session"
//
// "github.com/allegro/bigcache/v3"
//)
//
//var publicRoutes = map[string]bool{
// "/home.html": true,
// "/dashboard": true,
// "/system/menus": true,
// "/upload/img": true,
// "/upload/file": true,
// "/upload/multi_files": true,
// "/pear.json": true,
// "/logout": true,
//}
//
//// MenuCacheEntry 菜单缓存条目
//type MenuCacheEntry struct {
// Data map[string]*dto.OwnerMenuDto `json:"data"`
// Timestamp int64 `json:"timestamp"`
// Version int64 `json:"version"`
//}
//
//// CacheStats 缓存统计信息
//type CacheStats struct {
// mu sync.RWMutex
// Hits int64
// Misses int64
// Errors int64
// RefreshCount int64
// AsyncRefreshHits int64
// LastRefreshTime time.Time
//}
//
//// GetHitRate 获取命中率
//func (cs *CacheStats) GetHitRate() float64 {
// cs.mu.RLock()
// defer cs.mu.RUnlock()
//
// total := cs.Hits + cs.Misses
// if total == 0 {
// return 0
// }
// return float64(cs.Hits) / float64(total) * 100
//}
//
//// MenuCacheManager BigCache菜单缓存管理器
//type MenuCacheManager struct {
// cache *bigcache.BigCache
// refreshTTL time.Duration
// stats *CacheStats
// mu sync.RWMutex
// refreshing map[int64]bool
// stopCh chan struct{}
// monitorTicker *time.Ticker
//}
//
//// NewMenuCacheManager 创建菜单缓存管理器
//func NewMenuCacheManager(ttl time.Duration) (*MenuCacheManager, error) {
// config := bigcache.DefaultConfig(ttl)
//
// // 生产环境优化配置
// config.Shards = 256 // 分片数,减少锁竞争
// config.MaxEntriesInWindow = 10000 // 窗口内最大条目数
// config.MaxEntrySize = 1024 * 50 // 最大条目50KB
// config.HardMaxCacheSize = 512 // 最大缓存512MB
// config.StatsEnabled = true // 启用统计
// config.Verbose = false // 关闭详细日志
// config.CleanWindow = 5 * time.Minute // 清理窗口
//
// ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
// defer cancel()
//
// cache, err := bigcache.New(ctx, config)
// if err != nil {
// return nil, fmt.Errorf("failed to create BigCache: %w", err)
// }
//
// manager := &MenuCacheManager{
// cache: cache,
// refreshTTL: time.Duration(float64(ttl) * 0.8), // 80%时间后开始刷新
// stats: &CacheStats{},
// refreshing: make(map[int64]bool),
// stopCh: make(chan struct{}),
// monitorTicker: time.NewTicker(time.Minute), // 每分钟监控一次
// }
//
// // 启动监控协程
// go manager.monitor()
//
// return manager, nil
//}
//
//// Get 获取缓存数据
//func (mcm *MenuCacheManager) Get(roleID int32) (map[string]*dto.OwnerMenuDto, bool, bool) {
// key := mcm.makeKey(roleID)
//
// data, err := mcm.cache.Get(key)
// if err != nil {
// if !errors.Is(err, bigcache.ErrEntryNotFound) {
// mcm.recordError()
// log.Printf("BigCache get error for roleID %d: %v", roleID, err)
// }
// mcm.recordMiss()
// return nil, false, false
// }
//
// // 反序列化
// var entry MenuCacheEntry
// if err := json.Unmarshal(data, &entry); err != nil {
// mcm.recordError()
// log.Printf("Failed to unmarshal cache entry for roleID %d: %v", roleID, err)
// mcm.recordMiss()
// return nil, false, false
// }
//
// mcm.recordHit()
//
// // 检查是否需要刷新
// needRefresh := time.Since(time.Unix(entry.Timestamp, 0)) > mcm.refreshTTL
//
// return entry.Data, true, needRefresh
//}
//
//// Set 设置缓存数据
//func (mcm *MenuCacheManager) Set(roleID int32, data map[string]*dto.OwnerMenuDto) error {
// key := mcm.makeKey(roleID)
//
// entry := MenuCacheEntry{
// Data: data,
// Timestamp: time.Now().Unix(),
// Version: time.Now().UnixNano(),
// }
//
// // 序列化
// entryData, err := json.Marshal(entry)
// if err != nil {
// mcm.recordError()
// return fmt.Errorf("failed to marshal cache entry: %w", err)
// }
//
// // 存储到BigCache
// err = mcm.cache.Set(key, entryData)
// if err != nil {
// mcm.recordError()
// return fmt.Errorf("failed to set cache: %w", err)
// }
//
// return nil
//}
//
//// Delete 删除缓存数据
//func (mcm *MenuCacheManager) Delete(roleID int32) error {
// key := mcm.makeKey(roleID)
// err := mcm.cache.Delete(key)
// if err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) {
// mcm.recordError()
// return fmt.Errorf("failed to delete cache: %w", err)
// }
// return nil
//}
//
//// makeKey 生成缓存key
//func (mcm *MenuCacheManager) makeKey(roleID int32) string {
// return "menu:role:" + strconv.Itoa(int(roleID))
//}
//
//// GetStats 获取统计信息
//func (mcm *MenuCacheManager) GetStats() *CacheStats {
// mcm.stats.mu.RLock()
// defer mcm.stats.mu.RUnlock()
//
// // 复制统计数据
// stats := *mcm.stats
// return &stats
//}
//
//// GetBigCacheStats 获取BigCache原生统计
//func (mcm *MenuCacheManager) GetBigCacheStats() bigcache.Stats {
// return mcm.cache.Stats()
//}
//
//// recordHit 记录命中
//func (mcm *MenuCacheManager) recordHit() {
// mcm.stats.mu.Lock()
// mcm.stats.Hits++
// mcm.stats.mu.Unlock()
//}
//
//// recordMiss 记录未命中
//func (mcm *MenuCacheManager) recordMiss() {
// mcm.stats.mu.Lock()
// mcm.stats.Misses++
// mcm.stats.mu.Unlock()
//}
//
//// recordError 记录错误
//func (mcm *MenuCacheManager) recordError() {
// mcm.stats.mu.Lock()
// mcm.stats.Errors++
// mcm.stats.mu.Unlock()
//}
//
//// recordRefresh 记录刷新
//func (mcm *MenuCacheManager) recordRefresh() {
// mcm.stats.mu.Lock()
// mcm.stats.RefreshCount++
// mcm.stats.LastRefreshTime = time.Now()
// mcm.stats.mu.Unlock()
//}
//
//// recordAsyncRefreshHit 记录异步刷新命中
//func (mcm *MenuCacheManager) recordAsyncRefreshHit() {
// mcm.stats.mu.Lock()
// mcm.stats.AsyncRefreshHits++
// mcm.stats.mu.Unlock()
//}
//
//// monitor 监控协程
//func (mcm *MenuCacheManager) monitor() {
// for {
// select {
// case <-mcm.monitorTicker.C:
// mcm.logStats()
// case <-mcm.stopCh:
// mcm.monitorTicker.Stop()
// return
// }
// }
//}
//
//// logStats 记录统计信息
//func (mcm *MenuCacheManager) logStats() {
// stats := mcm.GetStats()
// bigCacheStats := mcm.GetBigCacheStats()
//
// log.Printf("MenuCache Stats - Hits: %d, Misses: %d, Errors: %d, HitRate: %.2f%%, "+
// "RefreshCount: %d, AsyncRefreshHits: %d, BigCache Hits: %d, BigCache Misses: %d",
// stats.Hits, stats.Misses, stats.Errors, stats.GetHitRate(),
// stats.RefreshCount, stats.AsyncRefreshHits,
// bigCacheStats.Hits, bigCacheStats.Misses)
//}
//
//// Close 关闭缓存管理器
//func (mcm *MenuCacheManager) Close() error {
// close(mcm.stopCh)
// return mcm.cache.Close()
//}
//
//// CachedMenuService 带BigCache的菜单服务
//type CachedMenuService struct {
// menuService v1.MenuService
// cache *MenuCacheManager
// mu sync.RWMutex
// refreshing map[int32]bool
//}
//
//// NewCachedMenuService 创建带缓存的菜单服务
//func NewCachedMenuService(menuService v1.MenuService, cache *MenuCacheManager) *CachedMenuService {
// return &CachedMenuService{
// menuService: menuService,
// cache: cache,
// refreshing: make(map[int32]bool),
// }
//}
//
//// ListByRoleIDToMap 获取菜单数据带BigCache缓存
//func (cms *CachedMenuService) ListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
// // 尝试从缓存获取
// data, hit, needRefresh := cms.cache.Get(roleID)
// if hit {
// // 如果需要刷新且当前没有在刷新中,启动异步刷新
// if needRefresh && !cms.isRefreshing(roleID) {
// go cms.asyncRefresh(roleID)
// }
// return data, nil
// }
//
// // 缓存未命中,同步获取
// return cms.loadAndCache(ctx, roleID)
//}
//
//// loadAndCache 加载数据并缓存
//func (cms *CachedMenuService) loadAndCache(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
// // 防止并发重复加载
// cms.mu.Lock()
// if cms.refreshing[roleID] {
// cms.mu.Unlock()
// // 等待一小段时间后重试缓存
// time.Sleep(time.Millisecond * 5)
// if data, hit, _ := cms.cache.Get(roleID); hit {
// return data, nil
// }
// // 重试失败,继续加载
// cms.mu.Lock()
// }
// cms.refreshing[roleID] = true
// cms.mu.Unlock()
//
// defer func() {
// cms.mu.Lock()
// delete(cms.refreshing, roleID)
// cms.mu.Unlock()
// }()
//
// // 从原始服务获取数据
// data, err := cms.menuService.ListByRoleIDToMap(ctx, roleID)
// if err != nil {
// return nil, err
// }
//
// // 缓存数据
// if cacheErr := cms.cache.Set(roleID, data); cacheErr != nil {
// log.Printf("Failed to cache menu data for roleID %d: %v", roleID, cacheErr)
// // 缓存失败不影响业务逻辑,继续返回数据
// }
//
// return data, nil
//}
//
//// asyncRefresh 异步刷新缓存
//func (cms *CachedMenuService) asyncRefresh(roleID int32) {
// // 检查是否已在刷新中
// if cms.isRefreshing(roleID) {
// return
// }
//
// // 使用背景上下文
// ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
// defer cancel()
//
// _, err := cms.loadAndCache(ctx, roleID)
// if err != nil {
// log.Printf("Async refresh menu cache failed for roleID %d: %v", roleID, err)
// } else {
// cms.cache.recordRefresh()
// cms.cache.recordAsyncRefreshHit()
// }
//}
//
//// isRefreshing 检查是否正在刷新
//func (cms *CachedMenuService) isRefreshing(roleID int32) bool {
// cms.mu.RLock()
// refreshing := cms.refreshing[roleID]
// cms.mu.RUnlock()
// return refreshing
//}
//
//// InvalidateRole 使指定角色缓存失效
//func (cms *CachedMenuService) InvalidateRole(roleID int32) error {
// return cms.cache.Delete(roleID)
//}
//
//// GetCacheStats 获取缓存统计
//func (cms *CachedMenuService) GetCacheStats() *CacheStats {
// return cms.cache.GetStats()
//}
//
//// 全局实例
//var (
// menuCacheManager *MenuCacheManager
// cachedMenuSvc *CachedMenuService
// cacheInitOnce sync.Once
// cacheInitErr error
//)
//
//// InitMenuCache 初始化菜单缓存
//func InitMenuCache(menuService v1.MenuService) error {
// cacheInitOnce.Do(func() {
// // 缓存TTL设置为15分钟
// manager, err := NewMenuCacheManager(15 * time.Minute)
// if err != nil {
// cacheInitErr = fmt.Errorf("failed to initialize menu cache: %w", err)
// return
// }
//
// menuCacheManager = manager
// cachedMenuSvc = NewCachedMenuService(menuService, manager)
//
// log.Println("MenuCache initialized successfully with BigCache")
// })
//
// return cacheInitErr
//}
//
//// GetCachedMenuService 获取缓存菜单服务
//func GetCachedMenuService() *CachedMenuService {
// return cachedMenuSvc
//}
//
//// GetMenuCacheManager 获取缓存管理器
//func GetMenuCacheManager() *MenuCacheManager {
// return menuCacheManager
//}
//
//// CloseMenuCache 关闭菜单缓存
//func CloseMenuCache() error {
// if menuCacheManager != nil {
// return menuCacheManager.Close()
// }
// return nil
//}
//
//// Authorize 修改后的授权中间件
//func Authorize(
// sess session.Manager,
// menuService v1.MenuService,
//) func(http.Handler) http.Handler {
// // 初始化缓存
// if err := InitMenuCache(menuService); err != nil {
// log.Printf("Failed to initialize menu cache: %v", err)
// // 缓存初始化失败,降级到原始服务
// return func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// path := r.URL.Path
//
// // 登陆检查
// n := time.Now()
// user, err := sess.GetUser(ctx, know.StoreName)
// if err != nil || user.ID == 0 {
// http.Redirect(w, r, "/", http.StatusFound)
// return
// }
// log.Printf("scs get user: %s", time.Since(n).String())
//
// // 公共路由放行
// if publicRoutes[path] {
// ctx = setUser(ctx, user)
// next.ServeHTTP(w, r.WithContext(ctx))
// return
// }
//
// n1 := time.Now()
// // 权限检查 - 使用原始服务
// menus, err := menuService.ListByRoleIDToMap(ctx, user.RoleID)
// if err != nil || !hasPermission(menus, path) {
// http.Error(w, "Forbidden", http.StatusForbidden)
// return
// }
// log.Printf("listByRoleIDToMap (fallback): %s", time.Since(n1).String())
//
// n2 := time.Now()
// cur := getCurrentMenus(menus, path)
// log.Printf("getCurrentMenus: %s", time.Since(n2).String())
//
// ctx = setUser(ctx, user)
// ctx = setCurMenus(ctx, cur)
// next.ServeHTTP(w, r.WithContext(ctx))
// })
// }
// }
//
// return func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// path := r.URL.Path
//
// // 登陆检查
// n := time.Now()
// user, err := sess.GetUser(ctx, know.StoreName)
// if err != nil || user.ID == 0 {
// http.Redirect(w, r, "/", http.StatusFound)
// return
// }
// log.Printf("scs get user: %s", time.Since(n).String())
//
// // 公共路由放行
// if publicRoutes[path] {
// ctx = setUser(ctx, user)
// next.ServeHTTP(w, r.WithContext(ctx))
// return
// }
//
// n1 := time.Now()
// // 权限检查 - 使用BigCache缓存服务
// menus, err := GetCachedMenuService().ListByRoleIDToMap(ctx, user.RoleID)
// if err != nil || !hasPermission(menus, path) {
// http.Error(w, "Forbidden", http.StatusForbidden)
// return
// }
// log.Printf("listByRoleIDToMap (BigCache): %s", time.Since(n1).String())
//
// n2 := time.Now()
// cur := getCurrentMenus(menus, path)
// log.Printf("getCurrentMenus: %s", time.Since(n2).String())
//
// ctx = setUser(ctx, user)
// ctx = setCurMenus(ctx, cur)
// next.ServeHTTP(w, r.WithContext(ctx))
// })
// }
//}
//
//func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
// _, ok := menus[path]
// return ok
//}
//
//func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
// var res []dto.OwnerMenuDto
//
// menu, ok := data[path]
// if !ok {
// return res
// }
//
// for _, item := range data {
// 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
//}

View File

@@ -0,0 +1,121 @@
package mid
import (
"fmt"
"log"
"net/http"
"time"
"management/internal/erpserver/model/dto"
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/know"
"management/internal/pkg/session"
"github.com/patrickmn/go-cache"
)
var publicRoutes = map[string]bool{
"/home.html": true,
"/dashboard": true,
"/system/menus": true,
"/upload/img": true,
"/upload/file": true,
"/upload/multi_files": true,
"/pear.json": true,
"/logout": true,
}
// 定义一个全局的go-cache实例
var menuCache *cache.Cache
func init() {
// 初始化go-cache设置默认过期时间为5分钟每10分钟清理一次过期项
menuCache = cache.New(5*time.Minute, 10*time.Minute)
}
func Authorize(
sess session.Manager,
menuService v1.MenuService,
) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
path := r.URL.Path
// 登陆检查
user, err := sess.GetUser(ctx, know.StoreName)
if err != nil || user.ID == 0 {
http.Redirect(w, r, "/", http.StatusFound)
return
}
// 公共路由放行
if publicRoutes[path] {
ctx = setUser(ctx, user)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
n1 := time.Now()
// 权限检查
var menus map[string]*dto.OwnerMenuDto
cacheKey := fmt.Sprintf("user_menus:%d", user.RoleID) // 使用用户RoleID作为缓存key
// 尝试从内存缓存中获取菜单数据
if cachedMenus, found := menuCache.Get(cacheKey); found {
menus = cachedMenus.(map[string]*dto.OwnerMenuDto)
log.Printf("listByRoleIDToMap (from cache): %s", time.Since(n1).String())
} else {
// 内存缓存未命中从menuService获取并存入内存缓存
menus, err = menuService.ListByRoleIDToMap(ctx, user.RoleID)
if err != nil {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
menuCache.Set(cacheKey, menus, cache.DefaultExpiration) // 使用默认过期时间
log.Printf("listByRoleIDToMap (from service, then cached): %s", time.Since(n1).String())
}
if !hasPermission(menus, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
cur := getCurrentMenus(menus, path)
ctx = setUser(ctx, user)
ctx = setCurMenus(ctx, cur)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
_, ok := menus[path]
return ok
}
func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
var res []dto.OwnerMenuDto
menu, ok := data[path]
if !ok {
return res
}
for _, item := range data {
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
}

22
internal/pkg/mid/csrf.go Normal file
View File

@@ -0,0 +1,22 @@
package mid
import (
"fmt"
"net/http"
"github.com/justinas/nosurf"
)
func NoSurf(next http.Handler) http.Handler {
return nosurf.New(next)
}
func NoSurfContext(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := nosurf.Token(r)
ctx := setCsrfToken(r.Context(), token)
ctx = setHtmlCsrfToken(ctx, fmt.Sprintf(`<input type="hidden" name="csrf_token" value="%s" />`, token))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

75
internal/pkg/mid/mid.go Normal file
View File

@@ -0,0 +1,75 @@
package mid
import (
"context"
"management/internal/erpserver/model/dto"
"github.com/a-h/templ"
)
type userKey struct{}
func setUser(ctx context.Context, usr dto.AuthorizeUser) context.Context {
return context.WithValue(ctx, userKey{}, usr)
}
// GetUser returns the user from the context.
func GetUser(ctx context.Context) dto.AuthorizeUser {
v, ok := ctx.Value(userKey{}).(dto.AuthorizeUser)
if !ok {
return dto.AuthorizeUser{}
}
return v
}
type menuKey struct{}
func setCurMenus(ctx context.Context, ms []dto.OwnerMenuDto) context.Context {
return context.WithValue(ctx, menuKey{}, ms)
}
func GetCurMenus(ctx context.Context) []dto.OwnerMenuDto {
v, ok := ctx.Value(menuKey{}).([]dto.OwnerMenuDto)
if !ok {
return []dto.OwnerMenuDto{}
}
return v
}
type NoSurfToken struct {
Token string
HtmlToken string
}
type csrfKey struct{}
func setCsrfToken(ctx context.Context, token string) context.Context {
return context.WithValue(ctx, csrfKey{}, token)
}
func GetCsrfToken(ctx context.Context) string {
v, ok := ctx.Value(csrfKey{}).(string)
if !ok {
return ""
}
return v
}
type htmlCsrfKey struct{}
func setHtmlCsrfToken(ctx context.Context, token string) context.Context {
return context.WithValue(ctx, htmlCsrfKey{}, templ.Raw(token))
}
func GetHtmlCsrfToken(ctx context.Context) templ.Component {
v, ok := ctx.Value(htmlCsrfKey{}).(templ.Component)
if !ok {
return templ.Raw("")
}
return v
}

View File

@@ -1,4 +1,4 @@
package middleware
package mid
import (
"net/http"

View File

@@ -1,58 +0,0 @@
package middleware
import (
"context"
"errors"
"net/http"
"time"
systemmodel "management/internal/erpserver/model/system"
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/know"
"management/internal/pkg/session"
"github.com/drhin/logger"
"go.uber.org/zap"
)
func Audit(sess session.Manager, auditLogService v1.AuditLogService, log *logger.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 提前获取用户信息(同步操作)
user, err := sess.GetUser(r.Context(), know.StoreName)
if err != nil {
log.Error("获取用户会话失败", err)
next.ServeHTTP(w, r) // 继续处理请求
return
}
defer func() {
go func() {
if user.ID == 0 {
log.Error("用户信息为空", errors.New("scs get user is empty"))
return
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
al := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
if err := auditLogService.Create(ctx, al); err != nil {
log.Error(err.Error(), err,
zap.Int32("user_id", user.ID),
zap.String("user", user.Email),
zap.String("ip", al.Ip),
zap.String("os", al.Os),
zap.String("method", al.Method),
zap.String("path", al.Url),
)
}
}()
}()
next.ServeHTTP(w, r)
})
}
}

View File

@@ -1,60 +0,0 @@
package middleware
import (
"net/http"
"management/internal/erpserver/model/dto"
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/know"
"management/internal/pkg/session"
)
var publicRoutes = map[string]bool{
"/home.html": true,
"/dashboard": true,
"/system/menus": true,
"/upload/img": true,
"/upload/file": true,
"/upload/multi_files": true,
"/pear.json": true,
"/logout": true,
}
func Authorize(
sess session.Manager,
menuService v1.MenuService,
) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
path := r.URL.Path
// 登陆检查
user, err := sess.GetUser(ctx, know.StoreName)
if err != nil || user.ID == 0 {
http.Redirect(w, r, "/", http.StatusFound)
return
}
// 公共路由放行
if publicRoutes[path] {
next.ServeHTTP(w, r)
return
}
// 权限检查
menus, err := menuService.ListByRoleIDToMap(ctx, user.RoleID)
if err != nil || !hasPermission(menus, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
_, ok := menus[path]
return ok
}

View File

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

View File

@@ -61,7 +61,7 @@ func (r *render) setDefaultData(req *http.Request, data map[string]any) map[stri
ctx := req.Context()
authUser, err := r.session.GetUser(ctx, know.StoreName)
if err != nil || authUser == nil {
if err != nil || authUser.ID == 0 {
data["IsAuthenticated"] = false
} else {
data["IsAuthenticated"] = true

View File

@@ -1,83 +1,86 @@
package session
// import (
// "context"
// "time"
import (
"context"
"errors"
"time"
// "management/internal/pkg/redis"
// )
"github.com/redis/go-redis/v9"
)
// var (
// storePrefix = "scs:session:"
// ctx = context.Background()
// DefaultRedisStore = newRedisStore()
// )
// 为所有 Redis 操作定义一个合理的超时时间。
// 这个值应该根据你的服务SLA和网络状况来定通常在50-500毫秒之间是比较合理的。
// 最佳实践:这个值应该来自配置文件,而不是硬编码。
const redisTimeout = 200 * time.Millisecond
// type redisStore struct{}
// RedisStore 表示一个使用 go-redis/v9 客户端的 scs.Store 实现。
type RedisStore struct {
// 内嵌 go-redis 客户端
client *redis.Client
}
// func newRedisStore() *redisStore {
// return &redisStore{}
// }
// NewRedisStore 是 RedisStore 的构造函数。
func NewRedisStore(client *redis.Client) *RedisStore {
return &RedisStore{
client: client,
}
}
// // 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 方法根据 session token 从 Redis 中查找 session 数据。
// 如果 token 不存在或已过期exists 返回 false。
func (s *RedisStore) Find(token string) ([]byte, bool, error) {
// ✅ 最佳实践: 为数据库操作创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
defer cancel()
// // 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
// }
// }
// 使用 go-redis 的 Get 方法
data, err := s.client.Get(ctx, token).Bytes()
if err != nil {
// 如果 key 不存在go-redis 会返回 redis.Nil 错误
if errors.Is(err, redis.Nil) {
return nil, false, nil
}
return nil, false, 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
// }
return data, true, nil
}
// t := time.Now()
// expired := exp.Sub(t)
// return redis.Set(ctx, storePrefix+token, b, expired)
// }
// Commit 方法将 session 数据和过期时间存入 Redis。
// 如果 token 已存在,则更新其数据和过期时间。
func (s *RedisStore) Commit(token string, b []byte, expiry time.Time) error {
// ✅ 最佳实践: 为数据库操作创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
defer cancel()
// // 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)
// 计算 Redis 的 TTL (Time To Live)
// time.Until(expiry) 会计算出当前时间到 expiry 之间的时间差
ttl := time.Until(expiry)
// 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
// }
// 使用 go-redis 的 Set 方法,并设置过期时间
// 如果 expiry 时间已经过去ttl 会是负数Redis 会立即删除这个 key这正是我们期望的行为。
err := s.client.Set(ctx, token, b, ttl).Err()
if err != nil {
return err
}
// if exists {
// sessions[token] = data
// }
// }
// if err := iter.Err(); err != nil {
// return nil, err
// }
return nil
}
// return sessions, nil
// }
// Delete 方法根据 session token 从 Redis 中删除 session 数据。
func (s *RedisStore) Delete(token string) error {
// ✅ 最佳实践: 为数据库操作创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
defer cancel()
// 使用 go-redis 的 Del 方法
err := s.client.Del(ctx, token).Err()
if err != nil {
return err
}
return nil
}

View File

@@ -10,9 +10,8 @@ import (
"management/internal/erpserver/model/dto"
"management/internal/pkg/config"
"github.com/alexedwards/scs/postgresstore"
"github.com/alexedwards/scs/v2"
"gorm.io/gorm"
"github.com/redis/go-redis/v9"
)
var ErrNoSession = errors.New("session user not found")
@@ -20,8 +19,8 @@ var ErrNoSession = errors.New("session user not found")
// Manager 抽象核心会话操作
type Manager interface {
Load(next http.Handler) http.Handler
GetUser(ctx context.Context, key string) (*dto.AuthorizeUser, error)
PutUser(ctx context.Context, key string, user *dto.AuthorizeUser) error
GetUser(ctx context.Context, key string) (dto.AuthorizeUser, error)
PutUser(ctx context.Context, key string, user dto.AuthorizeUser) error
RenewToken(ctx context.Context) error
Destroy(ctx context.Context) error
}
@@ -30,7 +29,7 @@ type SCSSession struct {
manager *scs.SessionManager
}
func NewSCSManager(db *gorm.DB, config *config.Config) (Manager, error) {
func NewSCSManager(client *redis.Client, conf *config.Config) (Manager, error) {
sessionManager := scs.New()
sessionManager.Lifetime = 24 * time.Hour
sessionManager.IdleTimeout = 2 * time.Hour
@@ -38,21 +37,21 @@ func NewSCSManager(db *gorm.DB, config *config.Config) (Manager, error) {
sessionManager.Cookie.HttpOnly = true
sessionManager.Cookie.Persist = true
sessionManager.Cookie.SameSite = http.SameSiteStrictMode
sessionManager.Cookie.Secure = config.App.Prod
sessionManager.Cookie.Secure = conf.App.Prod
sqlDB, err := db.DB()
if err != nil {
return nil, err
}
//sqlDB, err := db.DB()
//if err != nil {
// return nil, err
//}
// postgres
// github.com/alexedwards/scs/postgresstore
sessionManager.Store = postgresstore.New(sqlDB)
// sessionManager.Store = postgresstore.New(sqlDB)
// pgx
// github.com/alexedwards/scs/pgxstore
// sessionManager.Store = pgxstore.New(pool)
// redis
// sessionManager.Store = newRedisStore()
sessionManager.Store = NewRedisStore(client)
return &SCSSession{manager: sessionManager}, nil
}
@@ -60,21 +59,21 @@ func (s *SCSSession) Load(next http.Handler) http.Handler {
return s.manager.LoadAndSave(next)
}
func (s *SCSSession) GetUser(ctx context.Context, key string) (*dto.AuthorizeUser, error) {
func (s *SCSSession) GetUser(ctx context.Context, key string) (dto.AuthorizeUser, error) {
data, ok := s.manager.Get(ctx, key).([]byte)
if !ok || len(data) == 0 {
return nil, ErrNoSession
return dto.AuthorizeUser{}, ErrNoSession
}
var user dto.AuthorizeUser
if err := json.Unmarshal(data, &user); err != nil {
return nil, err
return dto.AuthorizeUser{}, err
}
return &user, nil
return user, nil
}
func (s *SCSSession) PutUser(ctx context.Context, key string, user *dto.AuthorizeUser) error {
data, err := json.Marshal(user)
func (s *SCSSession) PutUser(ctx context.Context, key string, user dto.AuthorizeUser) error {
data, err := json.Marshal(&user)
if err != nil {
return err
}