# 22 add web - admin/login

This commit is contained in:
新亮
2021-04-10 15:05:50 +08:00
parent 66a5e29c9c
commit 8a6a34348f
88 changed files with 5499 additions and 1010 deletions
+33 -9
View File
@@ -16,22 +16,35 @@ const (
ResubmitError = 10106
ResubmitMsg = 10107
HashIdsDecodeError = 10108
SignatureError = 10109
// 模块级错误码 - 用户模块
// 业务模块级错误码
// 用户模块
IllegalUserName = 20101
UserCreateError = 20102
UserUpdateError = 20103
UserSearchError = 20104
// 授权调用方
AuthorizedCreateError = 30101
AuthorizedListError = 30102
AuthorizedDeleteError = 30103
AuthorizedUpdateError = 30104
AuthorizedDetailError = 30105
AuthorizedCreateAPIError = 30106
AuthorizedListAPIError = 30107
AuthorizedDeleteAPIError = 30108
AuthorizedCreateError = 20201
AuthorizedListError = 20202
AuthorizedDeleteError = 20203
AuthorizedUpdateError = 20204
AuthorizedDetailError = 20205
AuthorizedCreateAPIError = 20206
AuthorizedListAPIError = 20207
AuthorizedDeleteAPIError = 20208
// 管理员
AdminCreateError = 20301
AdminListError = 20302
AdminDeleteError = 20303
AdminUpdateError = 20304
AdminResetPasswordError = 20305
AdminLoginError = 20307
AdminLogOutError = 20308
AdminModifyPasswordError = 20309
AdminModifyPersonalInfoError = 20310
)
var codeText = map[int]string{
@@ -43,6 +56,7 @@ var codeText = map[int]string{
ResubmitError: "Resubmit Error",
ResubmitMsg: "请勿重复提交",
HashIdsDecodeError: "ID 参数有误",
SignatureError: "Signature Error",
IllegalUserName: "非法用户名",
UserCreateError: "创建用户失败",
@@ -57,6 +71,16 @@ var codeText = map[int]string{
AuthorizedCreateAPIError: "创建调用方API地址失败",
AuthorizedListAPIError: "获取调用方API地址列表失败",
AuthorizedDeleteAPIError: "删除调用方API地址失败",
AdminCreateError: "创建管理员失败",
AdminListError: "获取管理员列表页失败",
AdminDeleteError: "删除管理员失败",
AdminUpdateError: "更新管理员失败",
AdminResetPasswordError: "重置密码失败",
AdminLoginError: "登录失败",
AdminLogOutError: "退出失败",
AdminModifyPasswordError: "修改密码失败",
AdminModifyPersonalInfoError: "修改个人信息失败",
}
func Text(code int) string {
+68
View File
@@ -0,0 +1,68 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createRequest struct {
Username string `form:"username"` // 用户名
Nickname string `form:"nickname"` // 昵称
Mobile string `form:"mobile"` // 手机号
Password string `form:"password"` // 密码
}
type createResponse struct {
Id int32 `json:"id"` // 主键ID
}
// Create 新增管理员
// @Summary 新增管理员
// @Description 新增管理员
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param username formData string true "用户名"
// @Param nickname formData string true "昵称"
// @Param mobile formData string true "手机号"
// @Param password formData string true "密码"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin [post]
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
createData := new(admin_service.CreateAdminData)
createData.Nickname = req.Nickname
createData.Username = req.Username
createData.Mobile = req.Mobile
createData.Password = req.Password
id, err := h.adminService.Create(c, createData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminCreateError,
code.Text(code.AdminCreateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
+67
View File
@@ -0,0 +1,67 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteRequest struct {
Id string `uri:"id"` // HashID
}
type deleteResponse struct {
Id int32 `json:"id"` // 主键ID
}
// Delete 删除管理员
// @Summary 删除管理员
// @Description 删除管理员
// @Tags API.admin
// @Accept json
// @Produce json
// @Param id path string true "hashId"
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/{id} [delete]
func (h *handler) Delete() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteRequest)
res := new(deleteResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
)
return
}
id := int32(ids[0])
err = h.adminService.Delete(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminDeleteError,
code.Text(code.AdminDeleteError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,52 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
type detailResponse struct {
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
}
// Detail 管理员详情
// @Summary 管理员详情
// @Description 管理员详情
// @Tags API.admin
// @Accept json
// @Produce json
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/info [get]
func (h *handler) Detail() core.HandlerFunc {
return func(c core.Context) {
res := new(detailResponse)
searchOneData := new(admin_service.SearchOneData)
searchOneData.Id = cast.ToInt32(c.UserID())
searchOneData.IsUsed = 1
info, err := h.adminService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
res.Username = info.Username
res.Nickname = info.Nickname
res.Mobile = info.Mobile
c.Payload(res)
}
}
+138
View File
@@ -0,0 +1,138 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
"github.com/spf13/cast"
"go.uber.org/zap"
)
type listRequest struct {
Page int `form:"page"` // 第几页
PageSize int `form:"page_size"` // 每页显示条数
Username string `form:"username"` // 用户名
Nickname string `form:"nickname"` // 昵称
Mobile string `form:"mobile"` // 手机号
}
type listData struct {
Id int `json:"id"` // ID
HashID string `json:"hashid"` // hashid
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
IsUsed int `json:"is_used"` // 是否启用 1:是 -1:否
CreatedAt string `json:"created_at"` // 创建时间
CreatedUser string `json:"created_user"` // 创建人
UpdatedAt string `json:"updated_at"` // 更新时间
UpdatedUser string `json:"updated_user"` // 更新人
}
type listResponse struct {
List []listData `json:"list"`
Pagination struct {
Total int `json:"total"`
CurrentPage int `json:"current_page"`
PrePageCount int `json:"pre_page_count"`
} `json:"pagination"`
}
// List 管理员列表
// @Summary 管理员列表
// @Description 管理员列表
// @Tags API.admin
// @Accept json
// @Produce json
// @Param page query int false "第几页"
// @Param page_size query string false "每页显示条数"
// @Param username query string false "用户名"
// @Param nickname query string false "昵称"
// @Param mobile query string false "手机号"
// @Success 200 {object} listResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin [get]
func (h *handler) List() core.HandlerFunc {
return func(c core.Context) {
req := new(listRequest)
res := new(listResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
page := req.Page
if page == 0 {
page = 1
}
pageSize := req.PageSize
if pageSize == 0 {
pageSize = 10
}
searchData := new(admin_service.SearchData)
searchData.Page = page
searchData.PageSize = pageSize
searchData.Username = req.Username
searchData.Nickname = req.Nickname
searchData.Mobile = req.Mobile
resListData, err := h.adminService.PageList(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminListError,
code.Text(code.AdminListError)).WithErr(err),
)
return
}
resCountData, err := h.adminService.PageListCount(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminListError,
code.Text(code.AdminListError)).WithErr(err),
)
return
}
res.Pagination.Total = cast.ToInt(resCountData)
res.Pagination.PrePageCount = pageSize
res.Pagination.CurrentPage = page
res.List = make([]listData, len(resListData))
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
h.logger.Info("hashids err", zap.Error(err))
}
data := listData{
Id: cast.ToInt(v.Id),
HashID: hashId,
Username: v.Username,
Nickname: v.Nickname,
Mobile: v.Mobile,
IsUsed: cast.ToInt(v.IsUsed),
CreatedAt: v.CreatedAt.Format(time_parse.CSTLayout),
CreatedUser: v.CreatedUser,
UpdatedAt: v.UpdatedAt.Format(time_parse.CSTLayout),
UpdatedUser: v.UpdatedUser,
}
res.List[k] = data
}
c.Payload(res)
}
}
@@ -0,0 +1,94 @@
package admin_handler
import (
"encoding/json"
"net/http"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
type loginRequest struct {
Username string `form:"username"` // 用户名
Password string `form:"password"` // 密码
}
type loginResponse struct {
Token string `json:"token"` // 用户身份标识
}
// Login 管理员登录
// @Summary 管理员登录
// @Description 管理员登录
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param username formData string true "用户名"
// @Param password formData string true "密码"
// @Success 200 {object} loginResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/login [post]
func (h *handler) Login() core.HandlerFunc {
return func(c core.Context) {
req := new(loginRequest)
res := new(loginResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
searchOneData := new(admin_service.SearchOneData)
searchOneData.Username = req.Username
searchOneData.Password = password.GeneratePassword(req.Password)
searchOneData.IsUsed = 1
info, err := h.adminService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
if info == nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(errors.New("未查询出符合条件的用户")),
)
return
}
token := password.GenerateLoginToken(info.Id)
// 用户信息
adminJsonInfo, _ := json.Marshal(info)
// 记录 Redis 中
err = h.cache.Set(h.adminService.CacheKeyPrefix()+token, string(adminJsonInfo), time.Hour*24, cache.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
res.Token = token
c.Payload(res)
}
}
@@ -0,0 +1,45 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
"github.com/spf13/cast"
)
type logoutResponse struct {
Username string `json:"username"` // 用户账号
}
// Logout 管理员登出
// @Summary 管理员登出
// @Description 管理员登出
// @Tags API.admin
// @Accept json
// @Produce json
// @Success 200 {object} logoutResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/login [post]
func (h *handler) Logout() core.HandlerFunc {
return func(c core.Context) {
res := new(logoutResponse)
res.Username = c.UserName()
if !h.cache.Del(h.adminService.CacheKeyPrefix()+password.GenerateLoginToken(cast.ToInt32(c.UserID())), cache.WithTrace(c.Trace())) {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLogOutError,
code.Text(code.AdminLogOutError)).WithErr(errors.New("cache del err")),
)
return
}
c.Payload(res)
}
}
@@ -0,0 +1,77 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
type modifyPasswordRequest struct {
OldPassword string `form:"old_password"` // 旧密码
NewPassword string `form:"new_password"` // 新密码
}
type modifyPasswordResponse struct {
Username string `json:"username"` // 用户账号
}
// ModifyPassword 修改密码
// @Summary 修改密码
// @Description 修改密码
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param old_password formData string true "旧密码"
// @Param new_password formData string true "新密码"
// @Success 200 {object} modifyPasswordResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/modify_password [patch]
func (h *handler) ModifyPassword() core.HandlerFunc {
return func(c core.Context) {
req := new(modifyPasswordRequest)
res := new(modifyPasswordResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
userId := cast.ToInt32(c.UserID())
searchOneData := new(admin_service.SearchOneData)
searchOneData.Id = userId
searchOneData.Password = password.GeneratePassword(req.OldPassword)
searchOneData.IsUsed = 1
info, err := h.adminService.Detail(c, searchOneData)
if err != nil || info == nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminModifyPasswordError,
code.Text(code.AdminModifyPasswordError)).WithErr(err),
)
return
}
if err := h.adminService.ModifyPassword(c, userId, req.NewPassword); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminModifyPasswordError,
code.Text(code.AdminModifyPasswordError)).WithErr(err),
)
return
}
res.Username = c.UserName()
c.Payload(res)
}
}
@@ -0,0 +1,65 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
type modifyPersonalInfoRequest struct {
Nickname string `form:"nickname"` // 昵称
Mobile string `form:"mobile"` // 手机号
}
type modifyPersonalInfoResponse struct {
Username string `json:"username"` // 用户账号
}
// ModifyPersonalInfo 修改个人信息
// @Summary 修改个人信息
// @Description 修改个人信息
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param nickname formData string true "昵称"
// @Param mobile formData string true "手机号"
// @Success 200 {object} modifyPersonalInfoResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/modify_password [patch]
func (h *handler) ModifyPersonalInfo() core.HandlerFunc {
return func(c core.Context) {
req := new(modifyPersonalInfoRequest)
res := new(modifyPersonalInfoResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
userId := cast.ToInt32(c.UserID())
modifyData := new(admin_service.ModifyData)
modifyData.Nickname = req.Nickname
modifyData.Mobile = req.Mobile
if err := h.adminService.ModifyPersonalInfo(c, userId, modifyData); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminModifyPersonalInfoError,
code.Text(code.AdminModifyPersonalInfoError)).WithErr(err),
)
return
}
res.Username = c.UserName()
c.Payload(res)
}
}
@@ -0,0 +1,67 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type resetPasswordRequest struct {
Id string `uri:"id"` // HashID
}
type resetPasswordResponse struct {
Id int32 `json:"id"` // 主键ID
}
// ResetPassword 重置密码
// @Summary 重置密码
// @Description 重置密码
// @Tags API.admin
// @Accept json
// @Produce json
// @Param id path string true "hashId"
// @Success 200 {object} resetPasswordResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/reset_password/{id} [patch]
func (h *handler) ResetPassword() core.HandlerFunc {
return func(c core.Context) {
req := new(resetPasswordRequest)
res := new(resetPasswordResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
)
return
}
id := int32(ids[0])
err = h.adminService.ResetPassword(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminResetPasswordError,
code.Text(code.AdminResetPasswordError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
+69
View File
@@ -0,0 +1,69 @@
package admin_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateUsedRequest struct {
Id string `form:"id"` // 主键ID
Used int32 `form:"used"` // 是否启用 1:是 -1:否
}
type updateUsedResponse struct {
Id int32 `json:"id"` // 主键ID
}
// UpdateUsed 更新管理员为启用/禁用
// @Summary 更新管理员为启用/禁用
// @Description 更新管理员为启用/禁用
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param id formData string true "Hashid"
// @Param used formData int true "是否启用 1:是 -1:否"
// @Success 200 {object} updateUsedResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/used [patch]
func (h *handler) UpdateUsed() core.HandlerFunc {
return func(c core.Context) {
req := new(updateUsedRequest)
res := new(updateUsedResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
)
return
}
id := int32(ids[0])
err = h.adminService.UpdateUsed(c, id, req.Used)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminUpdateError,
code.Text(code.AdminUpdateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,86 @@
package admin_handler
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/pkg/hash"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
i()
// Login 管理员登录
// @Tags API.admin
// @Router /api/admin/login [post]
Login() core.HandlerFunc
// Logout 管理员登出
// @Tags API.admin
// @Router /api/admin/logout [post]
Logout() core.HandlerFunc
// ModifyPassword 修改密码
// @Tags API.admin
// @Router /api/admin/modify_password [patch]
ModifyPassword() core.HandlerFunc
// Detail 个人信息
// @Tags API.admin
// @Router /api/admin/info [get]
Detail() core.HandlerFunc
// ModifyPersonalInfo 修改个人信息
// @Tags API.admin
// @Router /api/admin/modify_personal_info [patch]
ModifyPersonalInfo() core.HandlerFunc
// Create 新增管理员
// @Tags API.admin
// @Router /api/admin [post]
Create() core.HandlerFunc
// List 管理员列表
// @Tags API.admin
// @Router /api/admin [get]
List() core.HandlerFunc
// Delete 删除管理员
// @Tags API.admin
// @Router /api/admin/{id} [delete]
Delete() core.HandlerFunc
// UpdateUsed 更新管理员为启用/禁用
// @Tags API.admin
// @Router /api/admin/used [patch]
UpdateUsed() core.HandlerFunc
// ResetPassword 重置密码
// @Tags API.admin
// @Router /api/admin/reset_password/{id} [patch]
ResetPassword() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
hashids hash.Hash
adminService admin_service.Service
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
hashids: hash.New(configs.Get().HashIds.Secret, configs.Get().HashIds.Length),
adminService: admin_service.New(db, cache),
}
}
func (h *handler) i() {}
@@ -10,9 +10,9 @@ import (
)
type createRequest struct {
BusinessKey string `json:"business_key"` // 调用方key
BusinessDeveloper string `json:"business_developer"` // 调用方对接人
Remark string `json:"remark"` // 备注
BusinessKey string `form:"business_key"` // 调用方key
BusinessDeveloper string `form:"business_developer"` // 调用方对接人
Remark string `form:"remark"` // 备注
}
type createResponse struct {
@@ -23,9 +23,11 @@ type createResponse struct {
// @Summary 新增调用方
// @Description 新增调用方
// @Tags API.authorized
// @Accept json
// @Accept multipart/form-data
// @Produce json
// @Param Request body createRequest true "请求信息"
// @Param business_key formData string true "调用方key"
// @Param business_developer formData string true "调用方对接人"
// @Param remark formData string true "备注"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized [post]
@@ -33,7 +35,7 @@ func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindJSON(req); err != nil {
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
@@ -10,9 +10,9 @@ import (
)
type createAPIRequest struct {
Id string `json:"id"` // HashID
Method string `json:"method"` // 请求方法
API string `json:"api"` // 请求地址
Id string `form:"id"` // HashID
Method string `form:"method"` // 请求方法
API string `form:"api"` // 请求地址
}
type createAPIResponse struct {
@@ -23,9 +23,11 @@ type createAPIResponse struct {
// @Summary 授权调用方接口地址
// @Description 授权调用方接口地址
// @Tags API.authorized
// @Accept json
// @Accept multipart/form-data
// @Produce json
// @Param Request body createAPIRequest true "请求信息"
// @Param id formData string true "HashID"
// @Param method formData string true "请求方法"
// @Param api formData string true "请求地址"
// @Success 200 {object} createAPIResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized_api [post]
@@ -33,7 +35,7 @@ func (h *handler) CreateAPI() core.HandlerFunc {
return func(c core.Context) {
req := new(createAPIRequest)
res := new(createAPIResponse)
if err := c.ShouldBindJSON(req); err != nil {
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
@@ -22,7 +22,7 @@ type deleteAPIResponse struct {
// @Tags API.authorized
// @Accept json
// @Produce json
// @Param id path int true "主键ID"
// @Param id path string true "主键ID"
// @Success 200 {object} deleteAPIResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized_api/{id} [delete]
@@ -9,8 +9,8 @@ import (
)
type updateUsedRequest struct {
Id string `json:"id"` // 主键ID
Used int32 `json:"used"` // 是否启用 1:是 -1:否
Id string `form:"id"` // 主键ID
Used int32 `form:"used"` // 是否启用 1:是 -1:否
}
type updateUsedResponse struct {
@@ -21,9 +21,10 @@ type updateUsedResponse struct {
// @Summary 更新调用方为启用/禁用
// @Description 更新调用方为启用/禁用
// @Tags API.authorized
// @Accept json
// @Accept multipart/form-data
// @Produce json
// @Param Request body updateUsedRequest true "请求信息"
// @Param id formData string true "Hashid"
// @Param used formData int true "是否启用 1:是 -1:否"
// @Success 200 {object} updateUsedResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized/used [patch]
@@ -31,7 +32,7 @@ func (h *handler) UpdateUsed() core.HandlerFunc {
return func(c core.Context) {
req := new(updateUsedRequest)
res := new(updateUsedResponse)
if err := c.ShouldBindJSON(req); err != nil {
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
+583
View File
@@ -0,0 +1,583 @@
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package admin_repo
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *Admin {
return new(Admin)
}
func NewQueryBuilder() *adminRepoQueryBuilder {
return new(adminRepoQueryBuilder)
}
func (t *Admin) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *Admin) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *Admin) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&Admin{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type adminRepoQueryBuilder struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *adminRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *adminRepoQueryBuilder) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&Admin{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *adminRepoQueryBuilder) First(db *gorm.DB) (*Admin, error) {
ret := &Admin{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *adminRepoQueryBuilder) QueryOne(db *gorm.DB) (*Admin, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *adminRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*Admin, error) {
var ret []*Admin
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *adminRepoQueryBuilder) Limit(limit int) *adminRepoQueryBuilder {
qb.limit = limit
return qb
}
func (qb *adminRepoQueryBuilder) Offset(offset int) *adminRepoQueryBuilder {
qb.offset = offset
return qb
}
func (qb *adminRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIdIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIdNotIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderById(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "id "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereUsername(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "username", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUsernameIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "username", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUsernameNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "username", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByUsername(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "username "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WherePassword(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "password", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WherePasswordIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "password", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WherePasswordNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "password", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByPassword(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "password "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereNickname(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nickname", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereNicknameIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nickname", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereNicknameNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nickname", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByNickname(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "nickname "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereMobile(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereMobileIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereMobileNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByMobile(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "mobile "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsUsed(p db_repo.Predicate, value int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsUsedIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsUsedNotIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByIsUsed(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_used "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsDeletedIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsDeletedNotIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByIsDeleted(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_deleted "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedAtIn(value []time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedAtNotIn(value []time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByCreatedAt(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_at "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedUser(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedUserIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedUserNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByCreatedUser(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_user "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedAtIn(value []time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedAtNotIn(value []time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByUpdatedAt(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_at "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedUser(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedUserIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedUserNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByUpdatedUser(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_user "+order)
return qb
}
+19
View File
@@ -0,0 +1,19 @@
package admin_repo
import "time"
// 管理员表
//go:generate gormgen -structs Admin -input .
type Admin struct {
Id int32 // 主键
Username string // 用户名
Password string // 密码
Nickname string // 昵称
Mobile string // 手机号
IsUsed int32 // 是否启用 1:是 -1:否
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间
CreatedUser string // 创建人
UpdatedAt time.Time `gorm:"time"` // 更新时间
UpdatedUser string // 更新人
}
+16
View File
@@ -0,0 +1,16 @@
#### go_gin_api.admin
管理员表
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | |
| 2 | username | 用户名 | varchar(32) | UNI | NO | | |
| 3 | password | 密码 | varchar(32) | | NO | | |
| 4 | nickname | 昵称 | varchar(60) | | NO | | |
| 5 | mobile | 手机号 | varchar(20) | | NO | | |
| 6 | is_used | 是否启用 1:是 -1:否 | tinyint(1) | | NO | | 1 |
| 7 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 8 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 9 | created_user | 创建人 | varchar(60) | | NO | | |
| 10 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
| 11 | updated_user | 更新人 | varchar(60) | | NO | | |
@@ -0,0 +1,48 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
)
var _ Service = (*service)(nil)
// 定义缓存前缀
var cacheKeyPrefix = configs.ProjectName() + ":admin:"
type Service interface {
i()
CacheKeyPrefix() (pre string)
Create(ctx core.Context, authorizedData *CreateAdminData) (id int32, err error)
PageList(ctx core.Context, searchData *SearchData) (listData []*admin_repo.Admin, err error)
PageListCount(ctx core.Context, searchData *SearchData) (total int64, err error)
UpdateUsed(ctx core.Context, id int32, used int32) (err error)
Delete(ctx core.Context, id int32) (err error)
Detail(ctx core.Context, searchOneData *SearchOneData) (info *admin_repo.Admin, err error)
ResetPassword(ctx core.Context, id int32) (err error)
ModifyPassword(ctx core.Context, id int32, newPassword string) (err error)
ModifyPersonalInfo(ctx core.Context, id int32, modifyData *ModifyData) (err error)
}
type service struct {
db db.Repo
cache cache.Repo
}
func New(db db.Repo, cache cache.Repo) Service {
return &service{
db: db,
cache: cache,
}
}
func (s *service) i() {}
func (s *service) CacheKeyPrefix() (pre string) {
pre = cacheKeyPrefix
return
}
@@ -0,0 +1,31 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
type CreateAdminData struct {
Username string // 用户名
Nickname string // 昵称
Mobile string // 手机号
Password string // 密码
}
func (s *service) Create(ctx core.Context, adminData *CreateAdminData) (id int32, err error) {
model := admin_repo.NewModel()
model.Username = adminData.Username
model.Password = password.GeneratePassword(adminData.Password)
model.Nickname = adminData.Nickname
model.Mobile = adminData.Mobile
model.CreatedUser = ctx.UserName()
model.IsUsed = 1
model.IsDeleted = -1
id, err = model.Create(s.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}
@@ -0,0 +1,26 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
func (s *service) Delete(ctx core.Context, id int32) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,53 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type SearchOneData struct {
Id int32 // 用户ID
Username string // 用户名
Nickname string // 昵称
Mobile string // 手机号
Password string // 密码
IsUsed int32 // 是否启用 1:是 -1:否
}
func (s *service) Detail(ctx core.Context, searchOneData *SearchOneData) (info *admin_repo.Admin, err error) {
qb := admin_repo.NewQueryBuilder()
qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchOneData.Id != 0 {
qb.WhereId(db_repo.EqualPredicate, searchOneData.Id)
}
if searchOneData.Username != "" {
qb.WhereUsername(db_repo.EqualPredicate, searchOneData.Username)
}
if searchOneData.Nickname != "" {
qb.WhereNickname(db_repo.EqualPredicate, searchOneData.Nickname)
}
if searchOneData.Mobile != "" {
qb.WhereMobile(db_repo.EqualPredicate, searchOneData.Mobile)
}
if searchOneData.Password != "" {
qb.WherePassword(db_repo.EqualPredicate, searchOneData.Password)
}
if searchOneData.IsUsed != 0 {
qb.WhereIsUsed(db_repo.EqualPredicate, searchOneData.IsUsed)
}
info, err = qb.QueryOne(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,26 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
func (s *service) ModifyPassword(ctx core.Context, id int32, newPassword string) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"password": password.GeneratePassword(newPassword),
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,29 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type ModifyData struct {
Nickname string // 昵称
Mobile string // 手机号
}
func (s *service) ModifyPersonalInfo(ctx core.Context, id int32, modifyData *ModifyData) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"nickname": modifyData.Nickname,
"mobile": modifyData.Mobile,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
return
}
@@ -0,0 +1,56 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type SearchData struct {
Page int // 第几页
PageSize int // 每页显示条数
Username string // 用户名
Nickname string // 昵称
Mobile string // 手机号
}
func (s *service) PageList(ctx core.Context, searchData *SearchData) (listData []*admin_repo.Admin, err error) {
page := searchData.Page
if page == 0 {
page = 1
}
pageSize := searchData.PageSize
if pageSize == 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
qb := admin_repo.NewQueryBuilder()
qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchData.Username != "" {
qb.WhereUsername(db_repo.EqualPredicate, searchData.Username)
}
if searchData.Nickname != "" {
qb.WhereNickname(db_repo.EqualPredicate, searchData.Nickname)
}
if searchData.Mobile != "" {
qb.WhereMobile(db_repo.EqualPredicate, searchData.Mobile)
}
listData, err = qb.
Limit(pageSize).
Offset(offset).
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,31 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (s *service) PageListCount(ctx core.Context, searchData *SearchData) (total int64, err error) {
qb := admin_repo.NewQueryBuilder()
qb = qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchData.Username != "" {
qb.WhereUsername(db_repo.EqualPredicate, searchData.Username)
}
if searchData.Nickname != "" {
qb.WhereNickname(db_repo.EqualPredicate, searchData.Nickname)
}
if searchData.Mobile != "" {
qb.WhereMobile(db_repo.EqualPredicate, searchData.Mobile)
}
total, err = qb.Count(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}
@@ -0,0 +1,26 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
func (s *service) ResetPassword(ctx core.Context, id int32) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"password": password.ResetPassword(),
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,26 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_used": used,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}
@@ -1,6 +1,7 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -10,6 +11,9 @@ import (
var _ Service = (*service)(nil)
// 定义缓存前缀
var cacheKeyPrefix = configs.ProjectName() + ":authorized:"
type Service interface {
i()
@@ -20,6 +24,7 @@ type Service interface {
UpdateUsed(ctx core.Context, id int32, used int32) (err error)
Delete(ctx core.Context, id int32) (err error)
Detail(ctx core.Context, id int32) (info *authorized_repo.Authorized, err error)
DetailByKey(ctx core.Context, key string) (data *CacheAuthorizedData, err error)
CreateAPI(ctx core.Context, authorizedAPIData *CreateAuthorizedAPIData) (id int32, err error)
ListAPI(ctx core.Context, searchAPIData *SearchAPIData) (listData []*authorized_api_repo.AuthorizedApi, err error)
@@ -25,7 +25,7 @@ func (s *service) Create(ctx core.Context, authorizedData *CreateAuthorizedData)
model.BusinessSecret = secret
model.BusinessDeveloper = authorizedData.BusinessDeveloper
model.Remark = authorizedData.Remark
model.CreatedUser = "system" // TODO
model.CreatedUser = ctx.UserName()
model.IsUsed = 1
model.IsDeleted = -1
@@ -2,6 +2,7 @@ package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
@@ -16,12 +17,14 @@ func (s *service) CreateAPI(ctx core.Context, authorizedAPIData *CreateAuthorize
model.BusinessKey = authorizedAPIData.BusinessKey
model.Method = authorizedAPIData.Method
model.Api = authorizedAPIData.API
model.CreatedUser = "system" // TODO
model.CreatedUser = ctx.UserName()
model.IsDeleted = -1
id, err = model.Create(s.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
s.cache.Del(cacheKeyPrefix+authorizedAPIData.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}
@@ -1,17 +1,31 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"gorm.io/gorm"
)
func (s *service) Delete(ctx core.Context, id int32) (err error) {
// 先查询 id 是否存在
authorizedInfo, err := authorized_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereId(db_repo.EqualPredicate, id).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err == gorm.ErrRecordNotFound {
return nil
}
model := authorized_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": "system", // TODO
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
@@ -19,5 +33,6 @@ func (s *service) Delete(ctx core.Context, id int32) (err error) {
return err
}
s.cache.Del(cacheKeyPrefix+authorizedInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}
@@ -1,17 +1,31 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"gorm.io/gorm"
)
func (s *service) DeleteAPI(ctx core.Context, id int32) (err error) {
// 先查询 id 是否存在
authorizedApiInfo, err := authorized_api_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereId(db_repo.EqualPredicate, id).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err == gorm.ErrRecordNotFound {
return nil
}
model := authorized_api_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": "system", // TODO
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
@@ -19,5 +33,6 @@ func (s *service) DeleteAPI(ctx core.Context, id int32) (err error) {
return err
}
s.cache.Del(cacheKeyPrefix+authorizedApiInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}
@@ -8,8 +8,7 @@ import (
func (s *service) Detail(ctx core.Context, id int32) (info *authorized_repo.Authorized, err error) {
qb := authorized_repo.NewQueryBuilder()
qb = qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
qb.WhereId(db_repo.EqualPredicate, id)
info, err = qb.First(s.db.GetDbR().WithContext(ctx.RequestContext()))
@@ -0,0 +1,80 @@
package authorized_service
import (
"encoding/json"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
// 定义缓存结构
type CacheAuthorizedData struct {
Key string `json:"key"` // 调用方 key
Secret string `json:"secret"` // 调用方 secret
IsUsed int32 `json:"is_used"` // 调用方启用状态 1=启用 -1=禁用
Apis []cacheApiData `json:"apis"` // 调用方授权的 Apis
}
type cacheApiData struct {
Method string `json:"method"` // 请求方式
Api string `json:"api"` // 请求地址
}
func (s *service) DetailByKey(ctx core.Context, key string) (cacheData *CacheAuthorizedData, err error) {
// 查询缓存
cacheKey := cacheKeyPrefix + key
value, err := s.cache.Get(cacheKey, cache.WithTrace(ctx.RequestContext().Trace))
cacheData = new(CacheAuthorizedData)
if err == nil && json.Unmarshal([]byte(value), cacheData) == nil {
return
}
// 查询调用方信息
authorizedInfo, err := authorized_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereBusinessKey(db_repo.EqualPredicate, key).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
// 查询调用方授权 API 信息
authorizedApiInfo, err := authorized_api_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereBusinessKey(db_repo.EqualPredicate, key).
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
// 设置缓存 data
cacheData = new(CacheAuthorizedData)
cacheData.Key = key
cacheData.Secret = authorizedInfo.BusinessSecret
cacheData.IsUsed = authorizedInfo.IsUsed
cacheData.Apis = make([]cacheApiData, len(authorizedApiInfo))
for k, v := range authorizedApiInfo {
data := cacheApiData{
Method: v.Method,
Api: v.Api,
}
cacheData.Apis[k] = data
}
cacheDataByte, _ := json.Marshal(cacheData)
err = s.cache.Set(cacheKey, string(cacheDataByte), 0, cache.WithTrace(ctx.Trace()))
if err != nil {
return nil, err
}
return
}
@@ -1,17 +1,30 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"gorm.io/gorm"
)
func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error) {
authorizedInfo, err := authorized_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereId(db_repo.EqualPredicate, id).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err == gorm.ErrRecordNotFound {
return nil
}
model := authorized_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_used": used,
"updated_user": "system", // TODO
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
@@ -19,5 +32,6 @@ func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error)
return err
}
s.cache.Del(cacheKeyPrefix+authorizedInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}
+20 -5
View File
@@ -33,7 +33,7 @@ type Repo interface {
TTL(key string) (time.Duration, error)
Expire(key string, ttl time.Duration) bool
ExpireAt(key string, ttl time.Time) bool
Del(keys ...string) bool
Del(key string, options ...Option) bool
Exists(keys ...string) bool
Incr(key string, options ...Option) int64
Close() error
@@ -158,13 +158,28 @@ func (c *cacheRepo) Exists(keys ...string) bool {
return value > 0
}
// Del del some key from redis
func (c *cacheRepo) Del(keys ...string) bool {
if len(keys) == 0 {
func (c *cacheRepo) Del(key string, options ...Option) bool {
ts := time.Now()
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "del"
opt.Redis.Key = key
opt.Redis.CostSeconds = time.Since(ts).Seconds()
opt.Trace.AppendRedis(opt.Redis)
}
}()
for _, f := range options {
f(opt)
}
if key == "" {
return true
}
value, _ := c.client.Del(keys...).Result()
value, _ := c.client.Del(key).Result()
return value > 0
}
+14
View File
@@ -10,6 +10,7 @@ import (
"github.com/xinliangnote/go-gin-api/configs"
_ "github.com/xinliangnote/go-gin-api/docs"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/pkg/browser"
"github.com/xinliangnote/go-gin-api/pkg/color"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/errno"
@@ -48,6 +49,7 @@ type option struct {
recordMetrics RecordMetrics
enableCors bool
enableRate bool
enableOpenBrowser string
}
// OnPanicNotify 发生panic时通知用
@@ -93,6 +95,13 @@ func WithRecordMetrics(record RecordMetrics) Option {
}
}
// WithEnableOpenBrowser 启动后在浏览器中打开 uri
func WithEnableOpenBrowser(uri string) Option {
return func(opt *option) {
opt.enableOpenBrowser = uri
}
}
// WithEnableCors 开启CORS
func WithEnableCors() Option {
return func(opt *option) {
@@ -311,6 +320,11 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}))
}
if opt.enableOpenBrowser != "" {
_ = browser.Open(opt.enableOpenBrowser)
fmt.Println(color.Green("* [register open browser '" + opt.enableOpenBrowser + "']"))
}
// recover两次,防止处理时发生panic,尤其是在OnPanicNotify中。
mux.engine.Use(func(ctx *gin.Context) {
defer func() {
+45
View File
@@ -0,0 +1,45 @@
package password
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"fmt"
)
const salt = "qkhPAGA13HocW3GAEWwb"
const defaultPassword = "123456"
func GeneratePassword(str string) (password string) {
// md5
m := md5.New()
m.Write([]byte(str))
mByte := m.Sum(nil)
// hmac
h := hmac.New(sha256.New, []byte(salt))
h.Write(mByte)
password = hex.EncodeToString(h.Sum(nil))
return
}
func ResetPassword() (password string) {
m := md5.New()
m.Write([]byte(defaultPassword))
mStr := hex.EncodeToString(m.Sum(nil))
password = GeneratePassword(mStr)
return
}
func GenerateLoginToken(id int32) (token string) {
m := md5.New()
m.Write([]byte(fmt.Sprintf("%d%s", id, salt)))
token = hex.EncodeToString(m.Sum(nil))
return
}
@@ -0,0 +1,125 @@
package middleware
import (
"net/http"
"strings"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/signature"
"github.com/koketama/urltable"
"github.com/pkg/errors"
)
const ttl = time.Minute * 2 // 签名超时时间 2 分钟
var whiteListPath = map[string]bool{
"/login/web": true,
}
func (m *middleware) Signature() core.HandlerFunc {
return func(c core.Context) {
// 签名信息
authorization := c.GetHeader("Authorization")
if authorization == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New("Header 中缺少 Authorization 参数")),
)
return
}
// 时间信息
date := c.GetHeader("Authorization-Date")
if date == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New("Header 中缺少 Date 参数")),
)
return
}
// 通过签名信息获取 key
authorizationSplit := strings.Split(authorization, " ")
if len(authorizationSplit) < 2 {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New("Header 中 Authorization 格式错误")),
)
return
}
key := authorizationSplit[0]
data, err := m.authorizedService.DetailByKey(c, key)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(err),
)
return
}
// 验证 cache 是否被调用
if data.IsUsed == -1 {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New(key + " 已被禁止调用")),
)
return
}
if len(data.Apis) < 1 {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New(key + " 未进行接口授权")),
)
return
}
if !whiteListPath[c.Path()] {
// 验证 c.Method() + c.Path() 是否授权
table := urltable.NewTable()
for _, v := range data.Apis {
_ = table.Append(v.Method + v.Api)
}
if pattern, _ := table.Mapping(c.Method() + c.Path()); pattern == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New(c.Method() + c.Path() + " 未进行接口授权")),
)
return
}
}
ok, err := signature.New(key, data.Secret, ttl).Verify(authorization, date, c.Path(), c.Method(), c.RequestInputParams())
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(err),
)
return
}
if !ok {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New("Header 中 Authorization 信息错误")),
)
return
}
}
}
@@ -0,0 +1,57 @@
package middleware
import (
"encoding/json"
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
func (m *middleware) Token(ctx core.Context) (userId int64, userName string, err errno.Error) {
token := ctx.GetHeader("Token")
if token == "" {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Token 参数"))
return
}
if !m.cache.Exists(m.adminService.CacheKeyPrefix() + token) {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("请先登录"))
return
}
cacheData, cacheErr := m.cache.Get(m.adminService.CacheKeyPrefix()+token, cache.WithTrace(ctx.Trace()))
if cacheErr != nil {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(cacheErr)
return
}
type userInfo struct {
Id int64 `json:"id"` // 用户ID
Username string `json:"username"` // 用户名
}
var userData userInfo
_ = json.Unmarshal([]byte(cacheData), &userData)
userId = userData.Id
userName = userData.Username
return
}
+20 -5
View File
@@ -1,8 +1,11 @@
package middleware
import (
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/api/service/authorized_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"go.uber.org/zap"
@@ -22,17 +25,29 @@ type Middleware interface {
// DisableLog 不记录日志
DisableLog() core.HandlerFunc
// Signature 签名验证,对用签名算法 pkg/signature
Signature() core.HandlerFunc
// Token 签名验证,对登录用户的验证
Token(ctx core.Context) (userId int64, userName string, err errno.Error)
}
type middleware struct {
logger *zap.Logger
cache cache.Repo
logger *zap.Logger
cache cache.Repo
db db.Repo
authorizedService authorized_service.Service
adminService admin_service.Service
}
func New(logger *zap.Logger, cache cache.Repo) Middleware {
func New(logger *zap.Logger, cache cache.Repo, db db.Repo) Middleware {
return &middleware{
logger: logger,
cache: cache,
logger: logger,
cache: cache,
db: db,
authorizedService: authorized_service.New(db, cache),
adminService: admin_service.New(db, cache),
}
}
+2 -1
View File
@@ -29,6 +29,7 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C
}
mux, err := core.New(logger,
core.WithEnableOpenBrowser("http://127.0.0.1:9999"),
core.WithEnableCors(),
core.WithEnableRate(),
core.WithPanicNotify(notify.OnPanicNotify),
@@ -45,7 +46,7 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C
r.db = db
r.cache = cache
r.grpConn = grpConn
r.middles = middleware.New(logger, cache)
r.middles = middleware.New(logger, cache, db)
// 设置 WEB 路由
setWebRouter(r)
+25 -4
View File
@@ -1,6 +1,7 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/admin_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/authorized_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/tool_handler"
@@ -36,20 +37,40 @@ func setApiRouter(r *resource) {
user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
}
// authorized
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
// admin
adminHandler := admin_handler.New(r.logger, r.db, r.cache)
// 登录
login := r.mux.Group("/login", r.middles.Signature())
{
login.POST("/web", adminHandler.Login())
}
// api
api := r.mux.Group("/api")
api := r.mux.Group("/api", core.WrapAuthHandler(r.middles.Token), r.middles.Signature())
{
// authorized
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
api.POST("/authorized", authorizedHandler.Create())
api.GET("/authorized", authorizedHandler.List())
api.PATCH("/authorized/used", authorizedHandler.UpdateUsed())
api.DELETE("/authorized/:id", authorizedHandler.Delete())
api.POST("/authorized_api", authorizedHandler.CreateAPI())
api.GET("/authorized_list", authorizedHandler.ListAPI())
api.GET("/authorized_api", authorizedHandler.ListAPI())
api.DELETE("/authorized_api/:id", authorizedHandler.DeleteAPI())
api.POST("/admin", adminHandler.Create())
api.GET("/admin", adminHandler.List())
api.PATCH("/admin/used", adminHandler.UpdateUsed())
api.PATCH("/admin/reset_password/:id", adminHandler.ResetPassword())
api.DELETE("/admin/:id", adminHandler.Delete())
api.POST("/admin/logout", adminHandler.Logout())
api.PATCH("/admin/modify_password", adminHandler.ModifyPassword())
api.GET("/admin/info", adminHandler.Detail())
api.PATCH("/admin/modify_personal_info", adminHandler.ModifyPersonalInfo())
// tool
toolHandler := tool_handler.New(r.logger, r.db, r.cache)
api.GET("/tool/hashids/encode/:id", toolHandler.HashIdsEncode())
+9
View File
@@ -1,6 +1,7 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/web/controller/admin_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/authorized_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/configinfo_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/dashboard_handler"
@@ -17,6 +18,7 @@ func setWebRouter(r *resource) {
configInfoHandler := configinfo_handler.New(r.logger, r.db, r.cache)
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
toolHandler := tool_handler.New(r.logger, r.db, r.cache)
adminHandler := admin_handler.New(r.logger, r.db, r.cache)
web := r.mux.Group("", r.middles.DisableLog())
{
@@ -45,6 +47,13 @@ func setWebRouter(r *resource) {
web.GET("/authorized/api/:id", authorizedHandler.ApiView())
web.GET("/authorized/demo", authorizedHandler.DemoView())
// 管理员
web.GET("/admin/list", adminHandler.ListView())
web.GET("/admin/add", adminHandler.AddView())
web.GET("/admin/modify_password", adminHandler.ModifyPasswordView())
web.GET("/admin/modify_info", adminHandler.ModifyInfoView())
web.GET("/login", adminHandler.LoginView())
// 工具箱
web.GET("/tool/hashids", toolHandler.HashIdsView())
web.GET("/tool/logs", toolHandler.LogsView())
@@ -0,0 +1,9 @@
package admin_handler
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (h *handler) AddView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("admin_add", nil)
}
}
@@ -0,0 +1,9 @@
package admin_handler
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (h *handler) ListView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("admin_list", nil)
}
}
@@ -0,0 +1,9 @@
package admin_handler
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (h *handler) LoginView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("admin_login", nil)
}
}
@@ -0,0 +1,9 @@
package admin_handler
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (h *handler) ModifyInfoView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("admin_modifyinfo", nil)
}
}
@@ -0,0 +1,9 @@
package admin_handler
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (h *handler) ModifyPasswordView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("admin_modifypassword", nil)
}
}
@@ -0,0 +1,37 @@
package admin_handler
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
i()
AddView() core.HandlerFunc
ListView() core.HandlerFunc
LoginView() core.HandlerFunc
ModifyPasswordView() core.HandlerFunc
ModifyInfoView() core.HandlerFunc
}
type handler struct {
db db.Repo
logger *zap.Logger
cache cache.Repo
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
db: db,
}
}
func (h *handler) i() {}
@@ -15,14 +15,15 @@ type logsViewResponse struct {
}
type logData struct {
Level string `json:"level"`
Time string `json:"time"`
Path string `json:"path"`
HTTPCode int `json:"http_code"`
Method string `json:"method"`
Msg string `json:"msg"`
TraceID string `json:"trace_id"`
Content string `json:"content"`
Level string `json:"level"`
Time string `json:"time"`
Path string `json:"path"`
HTTPCode int `json:"http_code"`
Method string `json:"method"`
Msg string `json:"msg"`
TraceID string `json:"trace_id"`
Content string `json:"content"`
CostSeconds float64 `json:"cost_seconds"`
}
func (h *handler) LogsView() core.HandlerFunc {
@@ -59,14 +60,15 @@ func (h *handler) LogsView() core.HandlerFunc {
var logParse logParseData
_ = json.Unmarshal(content, &logParse)
data := logData{
Content: string(content),
Level: logParse.Level,
Time: logParse.Time,
Path: logParse.Path,
Method: logParse.Method,
Msg: logParse.Msg,
HTTPCode: logParse.HTTPCode,
TraceID: logParse.TraceID,
Content: string(content),
Level: logParse.Level,
Time: logParse.Time,
Path: logParse.Path,
Method: logParse.Method,
Msg: logParse.Msg,
HTTPCode: logParse.HTTPCode,
TraceID: logParse.TraceID,
CostSeconds: logParse.CostSeconds,
}
if string(content) != "" {