改造成api

This commit is contained in:
kenneth 2025-07-02 14:51:23 +08:00
parent c8a81d0f49
commit 39e91e85ba
27 changed files with 665 additions and 519 deletions

View File

@ -41,7 +41,7 @@ func Initialize(conf *config.Config, log *logger.Logger) (http.Handler, func(),
return nil, nil, err
}
token, err := token.NewPasetoMaker(conf.JWT.SigningKey)
tokenMaker, err := token.NewPasetoMaker(conf.JWT.SigningKey)
if err != nil {
dbClose()
rdClose()
@ -85,7 +85,7 @@ func Initialize(conf *config.Config, log *logger.Logger) (http.Handler, func(),
configService := system2.NewConfigService(service, configRepository)
captchaService := common.NewCaptchaService()
auditLogService := system2.NewAuditLogService(service, auditLogRepository)
authService := auth.NewAuth(log, rd, nil, userService, roleService, loginLogService)
authService := auth.NewAuth(conf, log, rd, tokenMaker, userService, roleService, loginLogService)
// =================================================================================================================
// task
@ -116,7 +116,7 @@ func Initialize(conf *config.Config, log *logger.Logger) (http.Handler, func(),
Conf: conf,
Log: log,
Render: rdr,
Token: token,
Token: tokenMaker,
TaskDistributor: taskDistributor,
CaptchaService: captchaService,
AuthService: authService,

View File

@ -20,7 +20,7 @@ redis:
db: 2
jwt:
signing_key: ec355d26-cca6-4347-a725-3c203ea1
expires_time: 15m
expires_time: 2h
refresh_time: 24h
captcha:
open_captcha: 10

View File

@ -1,31 +1,30 @@
package auth
import (
"log"
"management/internal/erpserver/model/system/request"
v1 "management/internal/erpserver/service/v1"
authv1 "management/internal/erpserver/service/v1/auth"
"management/internal/pkg/gin/gu"
"management/internal/pkg/mid"
"github.com/drhin/logger"
"github.com/gin-gonic/gin"
"github.com/zhang2092/browser"
)
type app struct {
log *logger.Logger
captchaService v1.CaptchaService
userService v1.UserService
authService *authv1.Auth
}
func newApp(
log *logger.Logger,
captchaService v1.CaptchaService,
userService v1.UserService,
authService *authv1.Auth,
) *app {
return &app{
log: log,
captchaService: captchaService,
userService: userService,
authService: authService,
@ -35,6 +34,7 @@ func newApp(
func (a *app) login(c *gin.Context) {
var req request.Login
if err := c.ShouldBindJSON(&req); err != nil {
log.Println("captchaID: ", req.CaptchaID)
gu.ValidatorErrors(c, err)
return
}
@ -48,7 +48,7 @@ func (a *app) login(c *gin.Context) {
req.Url = c.Request.URL.String()
req.Referrer = c.Request.Referer()
br, err := browser.NewBrowser(c.Request.UserAgent())
if err == nil {
if err == nil && br != nil {
req.Os = br.Platform().Name()
req.Browser = br.Name()
}
@ -62,6 +62,109 @@ func (a *app) login(c *gin.Context) {
gu.Ok(c, risk)
}
type Menu struct {
ID int `json:"id"`
Name string `json:"name"`
Url string `json:"url"`
Icon string `json:"icon"`
Child []Menu `json:"child"`
}
type User struct {
Name string `json:"name"`
Avatar string `json:"avatar"`
RoleID int32 `json:"role_id"`
}
type UserInfo struct {
User User `json:"user"`
Menus []Menu `json:"menus"`
}
func (a *app) getInfo(c *gin.Context) {
auth := mid.GetUser(c)
if auth == nil {
gu.Failed(c, "用户未登录")
return
}
user, err := a.userService.GetByUuid(c, auth.ID)
if err != nil {
gu.Failed(c, err.Error())
return
}
menus := []Menu{
{
ID: 1,
Name: "控制台",
Icon: "house",
Url: "",
Child: []Menu{
{
ID: 21,
Name: "后台首页",
Icon: "aim",
Url: "/",
},
},
},
{
ID: 1,
Name: "系统管理",
Icon: "setting",
Url: "",
Child: []Menu{
{
ID: 11,
Name: "菜单管理",
Icon: "menu",
Url: "/system/menu",
},
{
ID: 12,
Name: "角色管理",
Icon: "star",
Url: "/system/role",
},
{
ID: 12,
Name: "部门管理",
Icon: "clock",
Url: "/system/department",
},
{
ID: 13,
Name: "用户管理",
Icon: "user",
Url: "/system/user",
},
{
ID: 14,
Name: "登录日志",
Icon: "lock",
Url: "/loginlog/list",
},
{
ID: 15,
Name: "操作日志",
Icon: "lock",
Url: "/audit/list",
},
},
},
}
gu.Ok(c, UserInfo{
User: User{
Name: user.Username,
Avatar: user.Avatar,
RoleID: user.RoleID,
},
Menus: menus,
})
}
func (a *app) logout(c *gin.Context) {
gu.Ok(c, nil)
}

View File

@ -17,9 +17,11 @@ type Config struct {
}
func Routes(public *gin.RouterGroup, private *gin.RouterGroup, cfg Config) {
app := newApp(cfg.Log, cfg.CaptchaService, cfg.UserService, cfg.AuthService)
app := newApp(cfg.CaptchaService, cfg.UserService, cfg.AuthService)
public.POST("/login", app.login)
private.POST("user_info", app.getInfo)
private.GET("/logout", app.logout)
}

View File

@ -4,21 +4,18 @@ import (
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/config"
"management/internal/pkg/gin/gu"
"management/internal/pkg/render"
"github.com/gin-gonic/gin"
)
type app struct {
config *config.Config
render render.Renderer
captchaService v1.CaptchaService
}
func newApp(config *config.Config, render render.Renderer, captchaService v1.CaptchaService) *app {
func newApp(config *config.Config, captchaService v1.CaptchaService) *app {
return &app{
config: config,
render: render,
captchaService: captchaService,
}
}

View File

@ -3,18 +3,16 @@ package captcha
import (
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/config"
"management/internal/pkg/render"
"github.com/gin-gonic/gin"
)
type Config struct {
Conf *config.Config
Render render.Renderer
CaptchaService v1.CaptchaService
}
func Routes(r *gin.RouterGroup, cfg Config) {
app := newApp(cfg.Conf, cfg.Render, cfg.CaptchaService)
app := newApp(cfg.Conf, cfg.CaptchaService)
r.GET("/captcha", app.captcha)
}

View File

@ -45,8 +45,8 @@ func WebApp(cfg Config) http.Handler {
//app.Static("/public/*", "./public/")
publishApp := app.Group("/api")
privateApp := app.Group("/api")
publishApp := app.Group("")
privateApp := app.Group("")
privateApp.Use(mid.Authorize(cfg.Token, cfg.MenuService))
@ -61,16 +61,11 @@ func WebApp(cfg Config) http.Handler {
// 公共方法
captcha.Routes(publishApp, captcha.Config{
Conf: cfg.Conf,
Render: cfg.Render,
CaptchaService: cfg.CaptchaService,
})
upload.Routes(privateApp, upload.Config{
Conf: cfg.Conf,
Log: cfg.Log,
Render: cfg.Render,
TaskDistributor: cfg.TaskDistributor,
MenuService: cfg.MenuService,
Log: cfg.Log,
})
}

View File

@ -1,37 +0,0 @@
package config
import (
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/go-chi/chi/v5"
)
type Config struct {
Log *logger.Logger
Sm session.Manager
Render render.Renderer
TaskDistributor tasks.TaskDistributor
MenuService v1.MenuService
ConfigService v1.ConfigService
}
func Routes(r chi.Router, cfg Config) {
app := newApp(cfg.Render, cfg.ConfigService)
r.Get("/pear.json", app.pear)
r.Route("/config", func(r chi.Router) {
r.Use(mid.Audit(cfg.Sm, cfg.Log, cfg.TaskDistributor))
r.Get("/list", app.list)
r.Post("/list", app.list)
r.Get("/add", app.add)
r.Get("/edit", app.edit)
r.Post("/save", app.save)
r.Post("/refresh_cache", app.refreshCache)
r.Post("/reset_pear", app.resetPear)
})
}

View File

@ -1,6 +1,8 @@
package system
import (
"log"
"management/internal/erpserver/model/system/request"
"management/internal/erpserver/service/v1"
"management/internal/pkg/gin/gu"
@ -25,6 +27,8 @@ func (a *LoginLogApp) List(c *gin.Context) {
return
}
log.Println("hahaha")
res, count, err := a.loginLogService.List(c, req)
if err != nil {
gu.Failed(c, err.Error())

View File

@ -0,0 +1,256 @@
package system
import (
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/gin/gu"
"github.com/gin-gonic/gin"
)
const style = "layui-btn-primary layui-btn-sm"
type MenuApp struct {
menuService v1.MenuService
}
func NewMenuApp(menuService v1.MenuService) *MenuApp {
return &MenuApp{
menuService: menuService,
}
}
type Menu struct {
ID int `json:"id"`
Name string `json:"name"`
Url string `json:"url"`
Icon string `json:"icon"`
Child []Menu `json:"child"`
}
func (a *MenuApp) Menus(c *gin.Context) {
res := []Menu{
{
ID: 1,
Name: "系统管理",
Icon: "setting",
Url: "",
Child: []Menu{
{
ID: 11,
Name: "菜单管理",
Icon: "baseball",
Url: "/system/menu",
},
{
ID: 12,
Name: "角色管理",
Icon: "baseball",
Url: "/system/role",
},
{
ID: 12,
Name: "部门管理",
Icon: "baseball",
Url: "/system/department",
},
{
ID: 13,
Name: "用户管理",
Icon: "baseball",
Url: "/system/user",
},
{
ID: 14,
Name: "登录日志",
Icon: "baseball",
Url: "/system/login_log",
},
{
ID: 15,
Name: "操作日志",
Icon: "baseball",
Url: "/system/audit_log",
},
},
},
}
gu.Ok(c, res)
}
//
//func (a *MenuApp) list(w http.ResponseWriter, r *http.Request) {
// switch r.Method {
// case http.MethodGet:
// ctx := r.Context()
// a.render.Render(ctx, w, menu.List(ctx))
// case http.MethodPost:
// res, err := a.menuService.ListMenuTree(r.Context())
// if err != nil {
// a.render.JSONErr(w, err.Error())
// return
// }
// a.render.JSON(w, render.NewResponseList(0, res))
// default:
// a.render.JSONErr(w, "method not allowed")
// }
//}
//
//func (a *MenuApp) add(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// a.render.Render(ctx, w, menu.Edit(ctx, &systemmodel.Menu{Style: style, Visible: true, Sort: 6666}))
//}
//
//func (a *MenuApp) addChildren(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// vars := r.URL.Query()
// parentID := convertor.QueryInt[int32](vars, "parentID", 0)
// vm := &systemmodel.Menu{ParentID: parentID, Style: style, Visible: true, Sort: 6666}
// parent, err := a.menuService.Get(ctx, parentID)
// if err == nil {
// if parent.Type == "node" {
// vm.Type = "menu"
// } else if parent.Type == "menu" {
// vm.Type = "btn"
// }
// }
// a.render.Render(ctx, w, menu.Edit(ctx, vm))
//}
//
//func (a *MenuApp) edit(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// vars := r.URL.Query()
// id := convertor.QueryInt[int32](vars, "id", 0)
// vm := &systemmodel.Menu{Style: style, Sort: 6666}
// if id > 0 {
// vm, _ = a.menuService.Get(r.Context(), id)
// }
// a.render.Render(ctx, w, menu.Edit(ctx, vm))
//}
//
//func (a *MenuApp) save(w http.ResponseWriter, r *http.Request) {
// id := convertor.Int[int32](r.PostFormValue("ID"), 0)
// name := r.PostFormValue("Name")
// displayName := r.PostFormValue("DisplayName")
// t := r.PostFormValue("Type")
// url := r.PostFormValue("Url")
// if len(url) == 0 {
// url = uuid.Must(uuid.NewRandom()).String()
// }
// avatar := r.PostFormValue("Avatar")
// style := r.PostFormValue("Style")
// parentID := convertor.Int[int32](r.PostFormValue("ParentID"), 0)
// visible := convertor.Bool(r.PostFormValue("Visible"), false)
// isList := convertor.Bool(r.PostFormValue("IsList"), false)
// sort := convertor.Int[int32](r.PostFormValue("Sort"), 6666)
// status := convertor.Int[int32](r.PostFormValue("Status"), 0)
//
// ctx := r.Context()
// if len(avatar) > 0 && !strings.Contains(avatar, "layui-icon ") {
// avatar = "layui-icon " + avatar
// }
//
// parentPath := ""
// if parentID > 0 {
// parent, err := a.menuService.Get(ctx, parentID)
// if err != nil {
// a.render.JSONErr(w, err.Error())
// return
// }
// parentPath = parent.ParentPath + "," + strconv.Itoa(int(parentID)) + ","
// parentPath = strings.ReplaceAll(parentPath, ",,", ",")
// }
//
// if id == 0 {
// arg := &systemmodel.Menu{
// Name: name,
// DisplayName: displayName,
// Url: url,
// Type: t,
// ParentID: parentID,
// ParentPath: parentPath,
// Avatar: avatar,
// Style: style,
// Visible: visible,
// IsList: isList,
// Status: status,
// Sort: sort,
// CreatedAt: time.Now(),
// UpdatedAt: time.Now(),
// }
// err := a.menuService.Create(ctx, arg)
// if err != nil {
// if database.IsUniqueViolation(err) {
// a.render.JSONErr(w, "菜单已存在")
// return
// }
// a.render.JSONErr(w, err.Error())
// return
// }
//
// a.render.JSONOk(w, "添加成功")
// } else {
// res, err := a.menuService.Get(ctx, id)
// if err != nil {
// a.render.JSONErr(w, err.Error())
// return
// }
//
// res.Name = name
// res.DisplayName = displayName
// res.Url = url
// res.Type = t
// res.ParentID = parentID
// res.ParentPath = parentPath
// res.Avatar = avatar
// res.Style = style
// res.Visible = visible
// res.IsList = isList
// res.Status = status
// res.Sort = sort
// res.UpdatedAt = time.Now()
// err = a.menuService.Update(ctx, res)
// if err != nil {
// a.render.JSONErr(w, err.Error())
// return
// }
//
// a.render.JSONOk(w, "更新成功")
// }
//}
//
//func (a *MenuApp) data(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// vars := r.URL.Query()
// t := vars.Get("type")
// if t == "tree" {
// res, err := a.menuService.Tree(ctx, 0)
// if err != nil {
// a.render.JSONErr(w, err.Error())
// return
// }
//
// a.render.JSON(w, res)
// return
// } else if t == "xm_select_tree" {
// res, err := a.menuService.XmSelectTree(ctx, 0)
// if err != nil {
// a.render.JSONErr(w, err.Error())
// return
// }
//
// a.render.JSON(w, res)
// return
// }
// a.render.JSON(w, nil)
//}
//
//func (a *MenuApp) refreshCache(w http.ResponseWriter, r *http.Request) {
// err := a.menuService.RefreshCache(r.Context())
// if err != nil {
// a.render.JSONErr(w, err.Error())
// return
// }
//
// a.render.JSONOk(w, "缓存刷新成功")
//}

View File

@ -1,220 +0,0 @@
package menu
import (
"net/http"
"strconv"
"strings"
"time"
systemmodel "management/internal/erpserver/model/system"
v1 "management/internal/erpserver/service/v1"
"management/internal/erpserver/templ/system/menu"
"management/internal/pkg/convertor"
"management/internal/pkg/database"
"management/internal/pkg/mid"
"management/internal/pkg/render"
"github.com/google/uuid"
)
const style = "layui-btn-primary layui-btn-sm"
type app struct {
render render.Renderer
menuService v1.MenuService
}
func newApp(render render.Renderer, menuService v1.MenuService) *app {
return &app{
render: render,
menuService: menuService,
}
}
func (a *app) menus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := mid.GetUser(ctx)
menus, err := a.menuService.OwerMenus(ctx, user.RoleID)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSON(w, menus)
}
func (a *app) list(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
ctx := r.Context()
a.render.Render(ctx, w, menu.List(ctx))
case http.MethodPost:
res, err := a.menuService.ListMenuTree(r.Context())
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSON(w, render.NewResponseList(0, res))
default:
a.render.JSONErr(w, "method not allowed")
}
}
func (a *app) add(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
a.render.Render(ctx, w, menu.Edit(ctx, &systemmodel.Menu{Style: style, Visible: true, Sort: 6666}))
}
func (a *app) addChildren(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := r.URL.Query()
parentID := convertor.QueryInt[int32](vars, "parentID", 0)
vm := &systemmodel.Menu{ParentID: parentID, Style: style, Visible: true, Sort: 6666}
parent, err := a.menuService.Get(ctx, parentID)
if err == nil {
if parent.Type == "node" {
vm.Type = "menu"
} else if parent.Type == "menu" {
vm.Type = "btn"
}
}
a.render.Render(ctx, w, menu.Edit(ctx, vm))
}
func (a *app) edit(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := r.URL.Query()
id := convertor.QueryInt[int32](vars, "id", 0)
vm := &systemmodel.Menu{Style: style, Sort: 6666}
if id > 0 {
vm, _ = a.menuService.Get(r.Context(), id)
}
a.render.Render(ctx, w, menu.Edit(ctx, vm))
}
func (a *app) save(w http.ResponseWriter, r *http.Request) {
id := convertor.Int[int32](r.PostFormValue("ID"), 0)
name := r.PostFormValue("Name")
displayName := r.PostFormValue("DisplayName")
t := r.PostFormValue("Type")
url := r.PostFormValue("Url")
if len(url) == 0 {
url = uuid.Must(uuid.NewRandom()).String()
}
avatar := r.PostFormValue("Avatar")
style := r.PostFormValue("Style")
parentID := convertor.Int[int32](r.PostFormValue("ParentID"), 0)
visible := convertor.Bool(r.PostFormValue("Visible"), false)
isList := convertor.Bool(r.PostFormValue("IsList"), false)
sort := convertor.Int[int32](r.PostFormValue("Sort"), 6666)
status := convertor.Int[int32](r.PostFormValue("Status"), 0)
ctx := r.Context()
if len(avatar) > 0 && !strings.Contains(avatar, "layui-icon ") {
avatar = "layui-icon " + avatar
}
parentPath := ""
if parentID > 0 {
parent, err := a.menuService.Get(ctx, parentID)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
parentPath = parent.ParentPath + "," + strconv.Itoa(int(parentID)) + ","
parentPath = strings.ReplaceAll(parentPath, ",,", ",")
}
if id == 0 {
arg := &systemmodel.Menu{
Name: name,
DisplayName: displayName,
Url: url,
Type: t,
ParentID: parentID,
ParentPath: parentPath,
Avatar: avatar,
Style: style,
Visible: visible,
IsList: isList,
Status: status,
Sort: sort,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err := a.menuService.Create(ctx, arg)
if err != nil {
if database.IsUniqueViolation(err) {
a.render.JSONErr(w, "菜单已存在")
return
}
a.render.JSONErr(w, err.Error())
return
}
a.render.JSONOk(w, "添加成功")
} else {
res, err := a.menuService.Get(ctx, id)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
res.Name = name
res.DisplayName = displayName
res.Url = url
res.Type = t
res.ParentID = parentID
res.ParentPath = parentPath
res.Avatar = avatar
res.Style = style
res.Visible = visible
res.IsList = isList
res.Status = status
res.Sort = sort
res.UpdatedAt = time.Now()
err = a.menuService.Update(ctx, res)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSONOk(w, "更新成功")
}
}
func (a *app) data(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := r.URL.Query()
t := vars.Get("type")
if t == "tree" {
res, err := a.menuService.Tree(ctx, 0)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSON(w, res)
return
} else if t == "xm_select_tree" {
res, err := a.menuService.XmSelectTree(ctx, 0)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSON(w, res)
return
}
a.render.JSON(w, nil)
}
func (a *app) refreshCache(w http.ResponseWriter, r *http.Request) {
err := a.menuService.RefreshCache(r.Context())
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSONOk(w, "缓存刷新成功")
}

View File

@ -3,15 +3,19 @@ package system
import (
v1 "management/internal/erpserver/service/v1"
"github.com/drhin/logger"
"github.com/gin-gonic/gin"
)
type Config struct {
MenuService v1.MenuService
AuditLogService v1.AuditLogService
ConfigService v1.ConfigService
LoginLogService v1.LoginLogService
RoleService v1.RoleService
Log *logger.Logger
MenuService v1.MenuService
AuditLogService v1.AuditLogService
ConfigService v1.ConfigService
LoginLogService v1.LoginLogService
RoleService v1.RoleService
UserService v1.UserService
DepartmentService v1.DepartmentService
}
func Routes(r *gin.RouterGroup, cfg Config) {
@ -20,28 +24,42 @@ func Routes(r *gin.RouterGroup, cfg Config) {
loginLogApp := NewLoginLogApp(cfg.LoginLogService)
configApp := NewConfigApp(cfg.ConfigService)
roleApp := NewRoleApp(cfg.RoleService, cfg.MenuService)
userApp := NewUserApp(cfg.Log, cfg.UserService, cfg.RoleService, cfg.DepartmentService)
menuApp := NewMenuApp(cfg.MenuService)
r.Group("/system", func(ctx *gin.Context) {
systemRouter := r.Group("/system")
{
// 审计日志
r.GET("/audit_log", auditApp.List)
systemRouter.GET("/audit_log", auditApp.List)
// 登陆日志
r.GET("/login_log", loginLogApp.List)
systemRouter.GET("/login_log", loginLogApp.List)
// 配置
r.POST("/config", configApp.Create)
r.PUT("/config/:id", configApp.Update)
r.GET("/config/:id", configApp.Get)
r.GET("/config", configApp.List)
systemRouter.POST("/config", configApp.Create)
systemRouter.PUT("/config/:id", configApp.Update)
systemRouter.GET("/config/:id", configApp.Get)
systemRouter.GET("/config", configApp.List)
systemRouter.POST("/refresh_cache", configApp.Refresh)
systemRouter.POST("/reset_pear", configApp.ResetPear)
// 用户
systemRouter.POST("/user", userApp.Create)
systemRouter.PUT("/user", userApp.Update)
systemRouter.GET("/user/:id", userApp.Get)
systemRouter.GET("/user", userApp.List)
// 角色
r.POST("/role", roleApp.Create)
r.PUT("/role/:id", roleApp.Update)
r.GET("/role/:id", roleApp.Get)
r.GET("/role", roleApp.List)
r.POST("/role/refresh_cache", roleApp.Refresh)
r.POST("/role/rebuild_parent_path", roleApp.RebuildParentPath)
r.POST("/role/refresh_role_menus", roleApp.RefreshRoleMenus)
r.POST("/role/set_menu", roleApp.setMenu)
})
systemRouter.POST("/role", roleApp.Create)
systemRouter.PUT("/role/:id", roleApp.Update)
systemRouter.GET("/role/:id", roleApp.Get)
systemRouter.GET("/role", roleApp.List)
systemRouter.POST("/role/refresh_cache", roleApp.Refresh)
systemRouter.POST("/role/rebuild_parent_path", roleApp.RebuildParentPath)
systemRouter.POST("/role/refresh_role_menus", roleApp.RefreshRoleMenus)
systemRouter.POST("/role/set_menu", roleApp.setMenu)
// 菜单
systemRouter.GET("/permission_menu", menuApp.Menus)
}
}

View File

@ -0,0 +1,114 @@
package system
import (
"errors"
"net/http"
"management/internal/erpserver/model/system/request"
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/gin/gu"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
"github.com/gin-gonic/gin"
)
type UserApp struct {
log *logger.Logger
userService v1.UserService
roleService v1.RoleService
departmentService v1.DepartmentService
}
func NewUserApp(
log *logger.Logger,
userService v1.UserService,
roleService v1.RoleService,
departmentService v1.DepartmentService,
) *UserApp {
return &UserApp{
log: log,
userService: userService,
roleService: roleService,
departmentService: departmentService,
}
}
func (a *UserApp) Create(c *gin.Context) {
var req request.CreateAndUpdateUser
if err := c.ShouldBindJSON(&req); err != nil {
gu.ValidatorErrors(c, err)
return
}
if err := a.userService.Create(c, &req); err != nil {
if errors.Is(err, sqldb.ErrDBDuplicatedEntry) {
gu.FailedWithCode(c, http.StatusConflict, "用户已存在")
return
}
gu.Failed(c, err.Error())
return
}
gu.Ok(c, "添加成功")
}
func (a *UserApp) Update(c *gin.Context) {
var id request.GetUserID
if err := c.ShouldBindUri(&id); err != nil {
gu.ValidatorErrors(c, err)
return
}
var req request.CreateAndUpdateUser
if err := c.ShouldBindJSON(&req); err != nil {
gu.ValidatorErrors(c, err)
return
}
req.ID = &id.ID
if err := a.userService.Update(c, &req); err != nil {
gu.Failed(c, err.Error())
return
}
gu.Ok(c, "更新成功")
}
func (a *UserApp) Get(c *gin.Context) {
var id request.GetUserID
if err := c.ShouldBindUri(&id); err != nil {
gu.ValidatorErrors(c, err)
return
}
user, err := a.userService.Get(c, id.ID)
if err != nil {
if errors.Is(err, sqldb.ErrDBNotFound) {
gu.FailedWithCode(c, http.StatusNotFound, "用户不存在")
return
}
gu.Failed(c, err.Error())
return
}
gu.Ok(c, user)
}
func (a *UserApp) List(c *gin.Context) {
var req request.ListUser
if err := c.ShouldBindQuery(&req); err != nil {
gu.ValidatorErrors(c, err)
return
}
res, count, err := a.userService.List(c, req)
if err != nil {
gu.Failed(c, err.Error())
return
}
gu.Ok(c, gu.NewPageData(count, req.PageID, req.PageSize, res))
}

View File

@ -1,152 +0,0 @@
package user
import (
"net/http"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/form"
systemmodel "management/internal/erpserver/model/system"
v1 "management/internal/erpserver/service/v1"
"management/internal/erpserver/templ/system/user"
"management/internal/pkg/binding"
"management/internal/pkg/convertor"
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"github.com/drhin/logger"
)
type app struct {
log *logger.Logger
sm session.Manager
render render.Renderer
userService v1.UserService
roleService v1.RoleService
departmentService v1.DepartmentService
}
func newApp(
log *logger.Logger,
sm session.Manager,
render render.Renderer,
userService v1.UserService,
roleService v1.RoleService,
departmentService v1.DepartmentService,
) *app {
return &app{
log: log,
sm: sm,
render: render,
userService: userService,
roleService: roleService,
departmentService: departmentService,
}
}
func (a *app) add(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
a.render.Render(ctx, w, user.Edit(ctx, &systemmodel.User{HashedPassword: nil}))
}
func (a *app) edit(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vm := &systemmodel.User{}
id := convertor.QueryInt[int32](r.URL.Query(), "id", 0)
if id > 0 {
if u, err := a.userService.Get(ctx, id); err == nil {
vm = u
vm.HashedPassword = []byte("********")
}
}
a.render.Render(ctx, w, user.Edit(ctx, vm))
}
func (a *app) save(w http.ResponseWriter, r *http.Request) {
var req form.User
if err := binding.Form.Bind(r, &req); err != nil {
a.render.JSONErr(w, binding.ValidatorErrors(err))
return
}
ctx := r.Context()
if req.DepartmentID > 0 {
if _, err := a.departmentService.Get(ctx, req.DepartmentID); err != nil {
a.render.JSONErr(w, "部门数据错误")
return
}
}
if req.RoleID > 0 {
if _, err := a.roleService.Get(ctx, req.RoleID); err != nil {
a.render.JSONErr(w, "角色数据错误")
return
}
}
if *req.ID == 0 {
err := a.userService.Create(ctx, &req)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSONOk(w, "添加成功")
} else {
err := a.userService.Update(ctx, &req)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSONOk(w, "更新成功")
}
}
func (a *app) list(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
ctx := r.Context()
a.render.Render(ctx, w, user.List(ctx))
case http.MethodPost:
var q dto.SearchDto
q.SearchTimeBegin, q.SearchTimeEnd = convertor.DefaultTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd"))
q.SearchStatus = convertor.Int(r.PostFormValue("status"), 9999)
q.SearchName = r.PostFormValue("name")
q.SearchEmail = r.PostFormValue("email")
q.SearchID = convertor.Int[int64](r.PostFormValue("id"), 0)
q.Page = convertor.Int(r.PostFormValue("page"), 1)
q.Rows = convertor.Int(r.PostFormValue("rows"), 10)
res, count, err := a.userService.List(r.Context(), q)
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSON(w, render.NewResponseList(count, res))
default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
}
func (a *app) profile(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
u := mid.GetUser(ctx)
vm, _ := a.userService.Get(ctx, u.ID)
a.render.Render(ctx, w, user.Profile(ctx, vm))
}
func (a *app) data(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
t := vars.Get("type")
if t == "xm_select" {
res, err := a.userService.XmSelect(r.Context())
if err != nil {
a.render.JSONErr(w, err.Error())
return
}
a.render.JSON(w, res)
return
}
a.render.JSON(w, nil)
}

View File

@ -1,25 +1,16 @@
package upload
import (
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/config"
"management/internal/pkg/render"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/gin-gonic/gin"
)
type Config struct {
Conf *config.Config
Log *logger.Logger
Render render.Renderer
TaskDistributor tasks.TaskDistributor
MenuService v1.MenuService
Log *logger.Logger
}
func Routes(r *gin.RouterGroup, cfg Config) {
app := newApp(cfg.Log, cfg.Render)
app := newApp(cfg.Log)
r.Group("/upload", func(ctx *gin.Context) {
r.POST("/img", app.img)

View File

@ -5,21 +5,18 @@ import (
fileutil "management/internal/pkg/file"
"management/internal/pkg/gin/gu"
"management/internal/pkg/render"
"github.com/drhin/logger"
"github.com/gin-gonic/gin"
)
type app struct {
log *logger.Logger
render render.Renderer
log *logger.Logger
}
func newApp(log *logger.Logger, render render.Renderer) *app {
func newApp(log *logger.Logger) *app {
return &app{
log: log,
render: render,
log: log,
}
}

View File

@ -7,10 +7,10 @@ import (
)
type Login struct {
Email string `form:"email" binding:"required,email"`
Password string `form:"password" binding:"required,min=6"`
Captcha string `form:"captcha" binding:"required"`
CaptchaID string `form:"captcha_id" binding:"required"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6"`
Captcha string `json:"captcha" binding:"required"`
CaptchaID string `json:"captcha_id" binding:"required"`
// 平台信息
Os string

View File

@ -0,0 +1,29 @@
package request
type GetUserID struct {
ID int32 `uri:"id" binding:"required,min=1"`
}
type CreateAndUpdateUser struct {
ID *int32 `form:"id"`
Email string `form:"email" binding:"required,email"`
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required,min=6"`
ChangePassword string `form:"change_password"`
Avatar string `form:"File"`
Gender int32 `form:"gender"`
DepartmentID int32 `form:"department_id"`
RoleID int32 `form:"role_id"`
Status *int32 `form:"status" binding:"required"`
}
type ListUser struct {
PageID int `form:"page_id" binding:"required,min=1"`
PageSize int `form:"page_size" binding:"required,min=5,max=20"`
StartTime string `form:"start_time"`
EndTime string `form:"end_time"`
ID int64 `form:"id"`
Name string `form:"name"`
Email string `form:"email"`
Status int32 `form:"status"`
}

View File

@ -4,7 +4,7 @@ import (
"context"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system/request"
"github.com/google/uuid"
)
@ -14,10 +14,11 @@ type UserRepository interface {
Create(ctx context.Context, obj *User) error
Update(ctx context.Context, obj *User) error
Get(ctx context.Context, id int32) (*User, error)
GetByUuid(ctx context.Context, uuid uuid.UUID) (*User, error)
GetByEmail(ctx context.Context, email string) (*User, error)
All(ctx context.Context) ([]*User, error)
Count(ctx context.Context, filter dto.SearchDto) (int64, error)
List(ctx context.Context, filter dto.SearchDto) ([]*User, error)
Count(ctx context.Context, filter request.ListUser) (int64, error)
List(ctx context.Context, filter request.ListUser) ([]*User, error)
}
type User struct {

View File

@ -132,8 +132,8 @@ func (s *store) HistoricalLogin(ctx context.Context, email string, createdAt tim
var wc []string
data["start_at"] = createdAt.Format(time.DateTime)
wc = append(wc, "created_at > :start_at")
data["created_at"] = createdAt.Format(time.DateTime)
wc = append(wc, "created_at < :created_at")
if email != "" {
data["email"] = email

View File

@ -4,35 +4,35 @@ import (
"bytes"
"strings"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system/request"
)
func applyFilter(filter dto.SearchDto, data map[string]any, buf *bytes.Buffer) {
func applyFilter(filter request.ListUser, data map[string]any, buf *bytes.Buffer) {
var wc []string
if filter.SearchTimeBegin != "" && filter.SearchTimeEnd == "" {
data["start_at"] = filter.SearchTimeBegin
data["end_at"] = filter.SearchTimeEnd
if filter.StartTime != "" && filter.EndTime != "" {
data["start_at"] = filter.StartTime
data["end_at"] = filter.EndTime
wc = append(wc, "created_at BETWEEN :start_at AND :end_at")
}
if filter.SearchID != 0 {
data["id"] = filter.SearchID
if filter.ID != 0 {
data["id"] = filter.ID
wc = append(wc, "id = :id")
}
if filter.SearchName != "" {
data["username"] = filter.SearchName
if filter.Name != "" {
data["username"] = filter.Name
wc = append(wc, "username LIKE :username")
}
if filter.SearchEmail != "" {
data["email"] = filter.SearchEmail
if filter.Email != "" {
data["email"] = filter.Email
wc = append(wc, "email LIKE :email")
}
if filter.SearchStatus != 9999 {
data["status"] = filter.SearchStatus
if filter.Status != 9999 {
data["status"] = filter.Status
wc = append(wc, "status = :status")
}

View File

@ -6,8 +6,8 @@ import (
"fmt"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/model/system/request"
"management/internal/erpserver/repository"
"management/internal/pkg/crypto"
"management/internal/pkg/rand"
@ -30,7 +30,7 @@ func NewStore(db *repository.Store, log *logger.Logger) system.UserRepository {
}
func (s *store) Initialize(ctx context.Context, departId, roleId int32) error {
count, err := s.Count(ctx, dto.SearchDto{})
count, err := s.Count(ctx, request.ListUser{})
if err != nil {
return err
}
@ -128,6 +128,30 @@ func (s *store) Get(ctx context.Context, id int32) (*system.User, error) {
return &user, nil
}
func (s *store) GetByUuid(ctx context.Context, uuid uuid.UUID) (*system.User, error) {
//goland:noinspection ALL
const q = `
SELECT
id, uuid, email, username, hashed_password, salt, avatar, gender, department_id,
role_id, status, change_password_at, created_at, updated_at
FROM
sys_user
WHERE
uuid = :uuid;`
data := map[string]any{
"uuid": uuid.String(),
}
var user system.User
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &user)
if err != nil {
return nil, fmt.Errorf("select uuid user: %w", err)
}
return &user, nil
}
func (s *store) GetByEmail(ctx context.Context, email string) (*system.User, error) {
//goland:noinspection ALL
const q = `
@ -172,7 +196,7 @@ func (s *store) All(ctx context.Context) ([]*system.User, error) {
return toPointer(users), nil
}
func (s *store) Count(ctx context.Context, filter dto.SearchDto) (int64, error) {
func (s *store) Count(ctx context.Context, filter request.ListUser) (int64, error) {
//goland:noinspection ALL
const q = `
SELECT
@ -197,7 +221,7 @@ func (s *store) Count(ctx context.Context, filter dto.SearchDto) (int64, error)
return count.Count, nil
}
func (s *store) List(ctx context.Context, filter dto.SearchDto) ([]*system.User, error) {
func (s *store) List(ctx context.Context, filter request.ListUser) ([]*system.User, error) {
//goland:noinspection ALL
const q = `
SELECT
@ -207,8 +231,8 @@ func (s *store) List(ctx context.Context, filter dto.SearchDto) ([]*system.User,
sys_user`
data := map[string]any{
"offset": (filter.Page - 1) * filter.Rows,
"rows_per_page": filter.Rows,
"offset": (filter.PageID - 1) * filter.PageSize,
"rows_per_page": filter.PageSize,
}
buf := bytes.NewBufferString(q)

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"net"
"net/http"
"strings"
@ -13,9 +14,9 @@ import (
"management/internal/erpserver/model/system"
"management/internal/erpserver/model/system/request"
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/config"
"management/internal/pkg/crypto"
"management/internal/pkg/know"
"management/internal/pkg/session"
"management/internal/pkg/token"
"github.com/drhin/logger"
"github.com/google/uuid"
@ -70,9 +71,10 @@ type LoginEnvironment struct {
// Auth 安全管理器
type Auth struct {
conf *config.Config
log *logger.Logger
redis *redis.Client
sm session.Manager
token token.Maker
userService v1.UserService
roleService v1.RoleService
@ -81,24 +83,32 @@ type Auth struct {
// NewAuth 创建安全管理器
func NewAuth(
conf *config.Config,
log *logger.Logger,
redis *redis.Client,
sm session.Manager,
token token.Maker,
userService v1.UserService,
roleService v1.RoleService,
loginLogService v1.LoginLogService,
) *Auth {
return &Auth{
conf: conf,
log: log,
redis: redis,
sm: sm,
token: token,
userService: userService,
roleService: roleService,
loginLogService: loginLogService,
}
}
func (a *Auth) Authenticate(ctx context.Context, req request.Login) (*RiskCheckResult, error) {
type AuthenticateResponse struct {
AccessToken string `json:"access_token"`
AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`
Risk *RiskCheckResult `json:"risk"`
}
func (a *Auth) Authenticate(ctx context.Context, req request.Login) (*AuthenticateResponse, error) {
l := system.NewLoginLog(req.Email, req.Os, req.Ip, req.Browser, req.Url, req.Referrer)
locked, duration, err := a.isAccountLocked(ctx, req.Email)
@ -146,13 +156,17 @@ func (a *Auth) Authenticate(ctx context.Context, req request.Login) (*RiskCheckR
}
}
// 设置会话Cookie
au := system.NewAuthorizeUser(user, req.Os, req.Ip, req.Browser)
if err := a.sm.PutUser(ctx, know.StoreName, au); err != nil {
// 生成token
accessToken, payload, err := a.token.CreateToken(user.Uuid, user.Username, a.conf.JWT.ExpiresTime, token.TypeAccessToken)
if err != nil {
return nil, err
}
return risk, nil
return &AuthenticateResponse{
AccessToken: accessToken,
AccessTokenExpiresAt: payload.ExpiredAt,
Risk: risk,
}, nil
}
func (a *Auth) validateUser(ctx context.Context, email, password string) (*system.User, error) {
@ -469,6 +483,7 @@ func (a *Auth) recordLoginLog(ctx context.Context, log *system.LoginLog) error {
func (a *Auth) getHistoricalLoginEnvironments(ctx context.Context, email string) ([]LoginEnvironment, error) {
rows, err := a.loginLogService.HistoricalLogin(ctx, email, time.Now().Add(-RiskCheckWindow))
if err != nil {
log.Println("获取历史登录环境失败111111:", err)
return nil, err
}

View File

@ -13,6 +13,7 @@ import (
"management/internal/pkg/session"
"github.com/drhin/logger"
"github.com/google/uuid"
)
type Service struct {
@ -50,11 +51,12 @@ type ConfigService interface {
}
type UserService interface {
Create(ctx context.Context, req *form.User) error
Update(ctx context.Context, req *form.User) error
Create(ctx context.Context, req *request.CreateAndUpdateUser) error
Update(ctx context.Context, req *request.CreateAndUpdateUser) error
All(ctx context.Context) ([]*system.User, error)
List(ctx context.Context, q dto.SearchDto) ([]*system.User, int64, error)
List(ctx context.Context, q request.ListUser) ([]*system.User, int64, error)
Get(ctx context.Context, id int32) (*system.User, error)
GetByUuid(ctx context.Context, uuid uuid.UUID) (*system.User, error)
GetByEmail(ctx context.Context, email string) (*system.User, error)
XmSelect(ctx context.Context) ([]*view.XmSelect, error)

View File

@ -6,9 +6,9 @@ import (
"strconv"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/form"
"management/internal/erpserver/model/system"
"management/internal/erpserver/model/system/request"
"management/internal/erpserver/model/view"
"management/internal/erpserver/service/v1"
"management/internal/pkg/crypto"
@ -44,7 +44,7 @@ func NewUserService(
}
}
func (s *userService) Create(ctx context.Context, req *form.User) error {
func (s *userService) Create(ctx context.Context, req *request.CreateAndUpdateUser) error {
salt, err := rand.String(10)
if err != nil {
return err
@ -85,7 +85,7 @@ func (s *userService) Create(ctx context.Context, req *form.User) error {
return nil
}
func (s *userService) Update(ctx context.Context, req *form.User) error {
func (s *userService) Update(ctx context.Context, req *request.CreateAndUpdateUser) error {
user, err := s.repo.Get(ctx, *req.ID)
if err != nil {
return err
@ -113,7 +113,7 @@ func (s *userService) All(ctx context.Context) ([]*system.User, error) {
return s.repo.All(ctx)
}
func (s *userService) List(ctx context.Context, q dto.SearchDto) ([]*system.User, int64, error) {
func (s *userService) List(ctx context.Context, q request.ListUser) ([]*system.User, int64, error) {
count, err := s.repo.Count(ctx, q)
if err != nil {
return nil, 0, err
@ -136,6 +136,10 @@ func (s *userService) Get(ctx context.Context, id int32) (*system.User, error) {
return s.repo.Get(ctx, id)
}
func (s *userService) GetByUuid(ctx context.Context, uuid uuid.UUID) (*system.User, error) {
return s.repo.GetByUuid(ctx, uuid)
}
func (s *userService) GetByEmail(ctx context.Context, email string) (*system.User, error) {
return s.repo.GetByEmail(ctx, email)
}

View File

@ -26,6 +26,7 @@ var publicRoutes = map[string]bool{
"/upload/file": true,
"/upload/multi_files": true,
"/logout": true,
"/user_info": true,
}
// 分片缓存配置
@ -147,13 +148,17 @@ func Authorize(
return
}
setUser(ctx, payload)
ctx.Next()
return
// 用户校验完毕, 开始校验权限
path := ctx.Request.URL.Path
// 公共路由放行
if publicRoutes[path] {
ctx.Set(authorizationPayloadKey, payload)
setUser(ctx, payload)
ctx.Next()
return
}

View File

@ -20,7 +20,7 @@ func setUser(ctx *gin.Context, payload *token.Payload) {
// GetUser returns the user from the context.
func GetUser(ctx *gin.Context) *token.Payload {
value, exists := ctx.Get(authorizationHeaderKey)
value, exists := ctx.Get(authorizationPayloadKey)
if !exists {
return nil
}