服务端工程师进化史-从零开始的APP开发(6)

3 篇文章 0 订阅
2 篇文章 0 订阅

前章

1.开发环境搭建
2.项目环境搭建
3.golang项目基础框架-前篇
4.golang项目基础框架-后篇
5.管理后台vue项目搭建

引言

经过前面几章的积累,已经形成前后端可对接的环境;本篇则是基于前期搭建的环境,进行纯粹的CRUD,将管理后台(菜单管理、用户管理、角色管理)作为示例,该项目如何进行开发。

项目地址

本次的代码仓库已更新到完整后台管理功能,api接口与vue后台页面都已上传。

# 管理后台前端
https://github.com/ZTLiao/liaz-admin-web.git
# 服务端
https://github.com/ZTLiao/liaz-server.git

设计

管理后台的基础:用户、菜单、角色,就这三者形成套简易的权限控制。
基于mysql输出数据库表设计:

drop table if exists admin_user;
create table admin_user(
admin_id bigint not null auto_increment comment '主键',
name varchar(64) not null comment '姓名',
username varchar(64) not null comment '账号',
password varchar(128) not null comment '密码',
phone varchar(64) comment '手机号码',
salt varchar(64) comment '盐',
avatar varchar(128) comment '头像',
email varchar(64) comment '邮箱',
introduction varchar(128) comment '描述',
status tinyint(4) default 0 comment '状态',
created_at timestamp(3) default current_timestamp(3) comment '创建时间',
updated_at timestamp(3) default current_timestamp(3) on update current_timestamp(3) comment '更新时间',
primary key(admin_id)
) comment '系统用户表';

drop table if exists admin_menu;
create table admin_menu(
menu_id bigint not null auto_increment comment '主键',
parent_id bigint default 0 comment '父级ID',
name varchar(64) comment '名称',
path varchar(64) comment '路径',
icon varchar(64) comment '图标',
status tinyint(4) default 0 comment '状态',
show_order int(11) default 0 comment '排序',
description varchar(128) comment '描述',
created_at timestamp(3) default current_timestamp(3) comment '创建时间',
updated_at timestamp(3) default current_timestamp(3) on update current_timestamp(3) comment '更新时间',
primary key(menu_id)
) comment '系统菜单表';

drop table if exists admin_role;
create table admin_role(
role_id bigint not null auto_increment comment '主键',
name varchar(64) comment '名称',
created_at timestamp(3) default current_timestamp(3) comment '创建时间',
updated_at timestamp(3) default current_timestamp(3) on update current_timestamp(3) comment '更新时间',
primary key(role_id)
) comment '系统角色表';

drop table if exists admin_role_menu;
create table admin_role_menu(
role_id bigint comment '角色ID',
menu_id bigint comment '菜单ID',
created_at timestamp(3) default current_timestamp(3) comment '创建时间',
key(role_id, menu_id)
) comment '系统角色菜单表';

drop table if exists admin_user_role;
create table admin_user_role(
admin_id bigint comment '用户ID',
role_id bigint comment '角色ID',
created_at timestamp(3) default current_timestamp(3) comment '创建时间',
key(admin_id, role_id)
) comment '系统用户角色表';

开发

先从服务端api开始,因本项目已有nacosxormgingo-redis,配置将放于nacos上;在golang项目基础框架-前篇中讲述nacos的加载过程。

application.yml

# 日志配置
logger:
 type: text
 path: ./logs/web_info.log 
 level: info
 # stdout 控制台 file 文件
 stdout: stdout

database.yaml

# mysql配置
database:
 driver: mysql
 url: tcp(127.0.0.1:3306)/liaz?charset=utf8&parseTime=True&loc=Local
 username: root
 password: 密码
 maxIdleConns: 10
 maxOpenConns: 100
 showSQL: true

# redis配置
redis:
 host: 127.0.0.1
 port: 6379
 password: ""
 db: 0
 minIdleConns: 0
 maxIdleConns: 50
 maxActiveConns: 100

# 对象存储配置
minio:
 endpoint: 127.0.0.1:9000
 accessKeyId: admin
 secretAccessKey: admin123
 secure: false

# 阿里云oss对象存储配置
oss:
 accessKeyId:
 accessKeySecret:
 bucketName:
 endpoint: 

# 腾讯云cos对象存储配置
cos:
 secretId: 
 secretKey: 
 serviceUrl: 
 bucketUrl: 

liaz-admin.yml

相对于不同模块定义名称不一样,主要取自于main.go

package main

import (
	_ "admin/router"
	"core/cmd"
)

func main() {
	cmd.Execute("liaz-admin")
}
# 服务端口
server:
 port: 8080

# 权限控制配置
security:
 excludes:
  - "/admin/login"

以用户管理作为示例

admin_user.go

定义AdminUser用户实体类

package model

import (
	"core/model"
	"core/types"
)

type AdminUser struct {
	AdminId      int64      `json:"adminId" xorm:"admin_id pk autoincr BIGINT"`
	Name         string     `json:"name" xorm:"name"`
	Username     string     `json:"username" xorm:"username"`
	Password     string     `json:"password" xorm:"password"`
	Salt         string     `json:"salt" xorm:"salt"`
	Phone        string     `json:"phone" xorm:"phone"`
	Avatar       string     `json:"avatar" xorm:"avatar"`
	Email        string     `json:"email" xorm:"email"`
	Introduction string     `json:"introduction" xorm:"introduction"`
	Status       int8       `json:"status" xorm:"status"`
	CreatedAt    types.Time `json:"createdAt" xorm:"created_at timestamp created"`
	UpdatedAt    types.Time `json:"updatedAt" xorm:"updated_at timestamp updated"`
}

var _ model.BaseModel = &AdminUser{}

func (e *AdminUser) TableName() string {
	return "admin_user"
}

core/model是实体类的基础接口,先设计预留(暂未用上)

package model

type BaseModel interface {
	TableName() string
}

core/types将golang的时间类json序列化为毫秒级时间戳,用于各个端接入通用结构;这么定义也是有原因的,golang的json序列化并没有提供时间类型time.Time转时间戳的工具类

package types

import (
	"core/utils"
	"strconv"
	"strings"
	"time"
)

type Time time.Time

func (t *Time) UnmarshalJSON(data []byte) (err error) {
	if len(data) == 2 {
		*t = Time(time.Time{})
		return
	}
	if strings.Contains(string(data), utils.DASHED) && strings.Contains(string(data), utils.DOT) {
		now, err := time.ParseInLocation(`"`+utils.NORM_DATETIME_MS_PATTERN+`"`, string(data), time.Local)
		*t = Time(now)
		return err
	} else if strings.Contains(string(data), utils.DASHED) {
		now, err := time.ParseInLocation(`"`+utils.NORM_DATETIME_PATTERN+`"`, string(data), time.Local)
		*t = Time(now)
		return err
	} else {
		timestamp, err := strconv.ParseInt(string(data), 10, 64)
		*t = Time(time.Unix(timestamp/1000, (timestamp%1000)*int64(time.Millisecond)))
		return err
	}
}

func (t Time) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(time.Time(t).UnixMilli()), 10)), nil
}

AdminUser类中,反单引号**`相当于java中的注解功能,扩展属性的作用,以下这行的定义就是json序列化后返回属性名与xorm**数据库表映射对应字段(pk:主键,autoincr:自增,BIGINT:数据类型)

`json:"adminId" xorm:"admin_id pk autoincr BIGINT"`

admin_user_db.go

xorm单表增删改查功能

package storage

import (
	"admin/enums"
	"admin/model"
	"core/types"
	"time"

	"github.com/go-xorm/xorm"
)

type AdminUserDb struct {
	db *xorm.Engine
}

func NewAdminUserDb(db *xorm.Engine) *AdminUserDb {
	return &AdminUserDb{db}
}

// 获取登录用户信息
func (e *AdminUserDb) GetLoginUser(username string, password string) (*model.AdminUser, error) {
	var adminUsers []model.AdminUser
	err := e.db.Where("(username = ? or phone = ? or email = ?) and password = ? and status = ?", username, username, username, password, enums.USER_STATUS_OF_ENABLE).Find(&adminUsers)
	if err != nil {
		return nil, err
	}
	if len(adminUsers) == 0 {
		return nil, nil
	}
	return &adminUsers[0], nil
}

func (e *AdminUserDb) GetAdminUserList() ([]model.AdminUser, error) {
	var adminUsers []model.AdminUser
	err := e.db.OrderBy("created_at desc").Find(&adminUsers)
	if err != nil {
		return nil, err
	}
	return adminUsers, nil
}

func (e *AdminUserDb) SaveOrUpdateAdminUser(adminUser *model.AdminUser) error {
	var now = types.Time(time.Now())
	adminId := adminUser.AdminId
	if adminId == 0 {
		adminUser.CreatedAt = now
		_, err := e.db.Insert(adminUser)
		if err != nil {
			return err
		}
	} else {
		adminUser.UpdatedAt = now
		_, err := e.db.ID(adminId).Update(adminUser)
		if err != nil {
			return err
		}
	}
	return nil
}

func (e *AdminUserDb) DelAdminUser(adminId int64) error {
	_, err := e.db.ID(adminId).Delete(&model.AdminUser{})
	if err != nil {
		return err
	}
	return nil
}

func (e *AdminUserDb) ThawAdminUser(adminId int64) error {
	_, err := e.db.ID(adminId).Update(&model.AdminUser{
		Status: 1,
	})
	if err != nil {
		return err
	}
	return nil
}

golang在数据库orm框架选用时,笔者纠结了好久,一直在xorm/gorm
/sqlx徘徊着,经过我一番调研,最终决定使用xorm;
用java的orm框架来解释:

gorm就相当于hibernate,xorm就是mybatis,而sqlx为spring-jdbc

admin_user_handler.go

接口业务逻辑层

package handler

import (
	"admin/model"
	"admin/resp"
	"admin/storage"
	"core/constant"
	"core/response"
	"core/web"
	"fmt"
	"net/http"
	"strconv"
)

type AdminUserHandler struct {
	AdminUserDb        *storage.AdminUserDb
	AdminLoginRecordDb *storage.AdminLoginRecordDb
	AdminUserCache     *storage.AdminUserCache
	AccessTokenCache   *storage.AccessTokenCache
}

func (e *AdminUserHandler) GetAdminUser(wc *web.WebContext) interface{} {
	accessToken := wc.GetHeader(constant.AUTHORIZATION)
	adminUser, err := e.AdminUserCache.Get(accessToken)
	if err != nil {
		wc.AbortWithError(err)
	}
	if adminUser == nil {
		return response.ReturnError(http.StatusForbidden, constant.ILLEGAL_REQUEST)
	}
	lastTime, err := e.AdminLoginRecordDb.GetLastTime(adminUser.AdminId)
	if err != nil {
		wc.AbortWithError(err)
	}
	return response.ReturnOK(&resp.AdminUserResp{
		AdminId:  adminUser.AdminId,
		Name:     adminUser.Name,
		Username: adminUser.Username,
		Avatar:   adminUser.Avatar,
		LastTime: lastTime,
	})
}

func (e *AdminUserHandler) GetAdminUserList(wc *web.WebContext) interface{} {
	adminUsers, err := e.AdminUserDb.GetAdminUserList()
	if err != nil {
		wc.AbortWithError(err)
	}
	return response.ReturnOK(adminUsers)
}

func (e *AdminUserHandler) SaveAdminUser(wc *web.WebContext) interface{} {
	e.saveOrUpdateAdminUser(wc)
	return response.Success()
}

func (e *AdminUserHandler) UpdateAdminUser(wc *web.WebContext) interface{} {
	e.saveOrUpdateAdminUser(wc)
	return response.Success()
}

func (e *AdminUserHandler) saveOrUpdateAdminUser(wc *web.WebContext) {
	var params map[string]any
	if err := wc.ShouldBindJSON(&params); err != nil {
		wc.AbortWithError(err)
	}
	adminIdStr := fmt.Sprint(params["adminId"])
	name := fmt.Sprint(params["name"])
	username := fmt.Sprint(params["username"])
	password := fmt.Sprint(params["password"])
	phone := fmt.Sprint(params["phone"])
	avatar := fmt.Sprint(params["avatar"])
	email := fmt.Sprint(params["email"])
	introduction := fmt.Sprint(params["introduction"])
	statusStr := fmt.Sprint(params["status"])
	var adminUser = new(model.AdminUser)
	if len(adminIdStr) > 0 {
		adminId, err := strconv.ParseInt(adminIdStr, 10, 64)
		if err != nil {
			wc.AbortWithError(err)
		}
		adminUser.AdminId = adminId
	}
	adminUser.Name = name
	adminUser.Username = username
	adminUser.Password = password
	adminUser.Phone = phone
	adminUser.Avatar = avatar
	adminUser.Email = email
	adminUser.Introduction = introduction
	status, err := strconv.ParseInt(statusStr, 10, 64)
	if err != nil {
		wc.AbortWithError(err)
	}
	adminUser.Status = int8(status)
	e.AdminUserDb.SaveOrUpdateAdminUser(adminUser)
}

func (e *AdminUserHandler) DelAdminUser(wc *web.WebContext) interface{} {
	adminIdStr := wc.Param("adminId")
	if len(adminIdStr) > 0 {
		adminId, err := strconv.ParseInt(adminIdStr, 10, 64)
		if err != nil {
			wc.AbortWithError(err)
		}
		e.AdminUserDb.DelAdminUser(adminId)
		accessToken, err := e.AccessTokenCache.Get(adminId)
		if err != nil {
			wc.AbortWithError(err)
		}
		if len(accessToken) > 0 {
			e.AdminUserCache.Del(accessToken)
		}
	}
	return response.Success()
}

func (e *AdminUserHandler) ThawAdminUser(wc *web.WebContext) interface{} {
	adminIdStr := wc.PostForm("adminId")
	if len(adminIdStr) > 0 {
		adminId, err := strconv.ParseInt(adminIdStr, 10, 64)
		if err != nil {
			wc.AbortWithError(err)
		}
		e.AdminUserDb.ThawAdminUser(adminId)
		accessToken, err := e.AccessTokenCache.Get(adminId)
		if err != nil {
			wc.AbortWithError(err)
		}
		if len(accessToken) > 0 {
			e.AdminUserCache.Del(accessToken)
		}
	}
	return response.Success()
}

admin_user_controller.go

api路由层

package controller

import (
	"admin/handler"
	"admin/storage"
	"core/redis"
	"core/system"
	"core/web"
)

type AdminUserController struct {
}

var _ web.IWebController = &AdminUserController{}

func (e *AdminUserController) Router(iWebRoutes web.IWebRoutes) {
	db := system.GetXormEngine()
	var redis = redis.NewRedisUtil(system.GetRedisClient())
	var adminUserHandler = handler.AdminUserHandler{
		AdminUserDb:        storage.NewAdminUserDb(db),
		AdminLoginRecordDb: storage.NewAdminLoginRecordDb(db),
		AdminUserCache:     storage.NewAdminUserCache(redis),
		AccessTokenCache:   storage.NewAccessTokenCache(redis),
	}
	iWebRoutes.GET("/user/get", adminUserHandler.GetAdminUser)
	iWebRoutes.GET("/user", adminUserHandler.GetAdminUserList)
	iWebRoutes.POST("/user", adminUserHandler.SaveAdminUser)
	iWebRoutes.PUT("/user", adminUserHandler.UpdateAdminUser)
	iWebRoutes.DELETE("/user/:adminId", adminUserHandler.DelAdminUser)
	iWebRoutes.PUT("/user/thaw", adminUserHandler.ThawAdminUser)
}

router.go

gin添加路由

package router

import (
	"admin/controller"
	"core/web"
)

func init() {
	//添加路由
	web.AddRouter(func(wrg *web.WebRouterGroup) {
		r := wrg.Group("/admin")
		{
			new(controller.AdminLoginController).Router(r)
			new(controller.AdminLogoutController).Router(r)
			new(controller.AdminUserController).Router(r)
			new(controller.AdminMenuController).Router(r)
			new(controller.AdminRoleController).Router(r)
			new(controller.AdminRoleMenuController).Router(r)
			new(controller.AdminUserRoleController).Router(r)
		}
	})
}

用户登录权限校验

func AdminSecurityHandler(security *config.Security, db *xorm.Engine, redis *redis.RedisUtil) gin.HandlerFunc {
	return func(c *gin.Context) {
		if !security.Encrypt {
			c.Next()
			return
		}
		requestUri := c.Request.RequestURI
		excludes := security.Excludes
		for _, v := range excludes {
			if requestUri == v {
				c.Next()
				return
			}
		}
		accessToken := c.Request.Header.Get(constant.AUTHORIZATION)
		if len(accessToken) == 0 {
			c.JSON(http.StatusUnauthorized, response.ReturnError(http.StatusUnauthorized, constant.UNAUTHORIZED))
			c.Abort()
			return
		}
		adminUser, err := storage.NewAdminUserCache(redis).Get(accessToken)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
		}
		if adminUser == nil {
			c.JSON(http.StatusUnauthorized, response.ReturnError(http.StatusUnauthorized, constant.UNAUTHORIZED))
			c.Abort()
			return
		}
		headers := request.GetHeaders(c)
		formParams, err := request.GetPostFormParams(c)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
		}
		queryParams := request.GetQueryParams(c)
		bodyParams, err := request.GetBodyParams(c)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
		}
		storage.NewAdminLogDb(db).AddLog(adminUser.AdminId, c.Request.RequestURI, headers, queryParams, formParams, bodyParams)
		c.Next()
	}
}

添加到gin的拦截函数中

func init() {
	//添加路由
	web.AddRouter(func(wrg *web.WebRouterGroup) {
		db := system.GetXormEngine()
		redis := redis.NewRedisUtil(system.GetRedisClient())
		wrg.Use(AdminSecurityHandler(config.SystemConfig.Security, db, redis))
		var success = func(wc *web.WebContext) interface{} {
			return response.Success()
		}
		wrg.Group("/").GET("/", success).HEAD("/", success)
		r := wrg.Group("/admin")
		{
			new(controller.AdminLoginController).Router(r)
			new(controller.AdminLogoutController).Router(r)
			new(controller.AdminUserController).Router(r)
			new(controller.AdminMenuController).Router(r)
			new(controller.AdminRoleController).Router(r)
			new(controller.AdminRoleMenuController).Router(r)
			new(controller.AdminUserRoleController).Router(r)
		}
	})
}

以上是用户管理CRUD功能

前端页面

<template>
    <section class="content">
        <div class="box box-danger">
            <div class="box-body">
                <!-- Content Header (Page header) -->
                <section class="content-header">
                    <h1 id="itemTitle"></h1>
                </section>
                <!-- .content -->
                <section class="content">
                    <div id="table"></div>
                    <div id="toolbar">
                        <button id="addBtn" class="btn btn-default">
                            <i class="glyphicon glyphicon-plus"></i>增加
                        </button>
                    </div>
                </section><!-- .content -->
            </div>
        </div>
    </section>
    <!-- .content -->
    <div class="modal fade" id="userModal" tabindex="-1" role="dialog" aria-labelledby="modalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                    <h4 class="modal-title" id="modalLabel">用户信息</h4>
                </div>
                <div class="modal-body">
                    <form class="form-horizontal">
                        <input type="hidden" name="adminId" v-model="adminUser.adminId" />
                        <div class="form-group">
                            <label for="name" class="col-sm-3 control-label">
                                <span style="color: red; ">*</span>姓名:
                            </label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control validate[required]" name="name" id="name"
                                    v-model="adminUser.name" />
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="username" class="col-sm-3 control-label">
                                <span style="color: red; ">*</span>账号:
                            </label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control validate[required]" name="username" id="username"
                                    v-model="adminUser.username" />
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-3 control-label">头像</label>
                            <div class="col-sm-8">
                                <input type="hidden" class="form-control" name="avatar" v-model="adminUser.avatar" />
                                <img :src="imgFormatter(adminUser.avatar)" style="width:120px;"/>
                                <input type="file" class="form-control" id="file" name="file">
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-3 control-label"></label>
                            <div class="col-sm-8">
                                <button class="btn btn-success col-sm-4" type="button" id="uploadBtn">上传</button>
                                <span class="btn col-sm-4" id="uploadInfo" style="color:red;"></span>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="email" class="col-sm-3 control-label">邮箱:</label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control" name="email" id="email" v-model="adminUser.email" />
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="phone" class="col-sm-3 control-label">
                                <span style="color: red; ">*</span>手机号码:
                            </label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control validate[required]" name="phone" id="phone"
                                    v-model="adminUser.phone">
                            </div>
                        </div>
                        <div class="form-group" id="passwordDiv">
                            <label for="password" class="col-sm-3 control-label">
                                <span style="color: red; ">*</span>密码:
                            </label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control validate[required]" name="password" id="password"
                                    v-model="adminUser.password">
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="status" class="col-sm-3 control-label">状态:</label>
                            <div class="col-sm-8">
                                <select name="status" id="status" data-btn-class="btn-warning">
                                    <option value="1">有效</option>
                                    <option value="0">无效</option>
                                </select>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="introduction" class="col-sm-3 control-label">描述:</label>
                            <div class="col-sm-8">
                                <textarea class="form-control" name="introduction" id="introduction"
                                    v-model="adminUser.introduction">
                                </textarea>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
                    <button type="button" class="btn btn-primary" id="save">保存</button>
                </div>
            </div>
        </div>
    </div>
    <div class="modal fade" id="roleModal" tabindex="-1" role="dialog" aria-labelledby="roleModalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                    <h4 class="modal-title" id="roleModalLabel">授权角色</h4>
                </div>
                <div class="modal-body">
                    <form class="form-horizontal">
                        <input type="hidden" name="adminId" v-model="adminUser.adminId" />
                        <div class="form-group">
                            <label for="adminUserName" class="col-sm-3 control-label">姓名:</label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control" name="adminUserName" id="adminUserName"
                                    disabled="disabled" v-model="adminUser.name" />
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-3 control-label">角色选项</label>
                            <div class="col-sm-8" id="roles">
                            </div>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
                    <button type="button" class="btn btn-primary" id="roleSave">保存</button>
                </div>
            </div>
        </div>
    </div>
    <div class="modal fade" id="roleMenuModal" tabindex="-1" role="dialog" aria-labelledby="roleMenuModalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content" style="width: 700px;">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                    <h4 class="modal-title" id="roleMenuModalLabel">授权菜单</h4>
                </div>
                <div class="modal-body">
                    <form class="form-horizontal">
                        <input type="hidden" name="roleIds" id="roleIds" />
                        <div class="form-group">
                            <label for="roleNames" class="col-sm-3 control-label">名称:</label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control" name="roleNames" disabled="disabled"
                                    id="roleNames" />
                            </div>
                        </div>
                        <div id="menuTree"></div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</template>
  
<script>
import TableHelper from '@/utils/bootstrap-table-helper';
import ComboboxHelper from '@/utils/bootstrap-combobox-helper';
import { getAdminUserList, saveAdminUser, updateAdminUser, delAdminUser, thawAdminUser } from '@/api/system/user';
import { getAdminUserRole, saveAdminUserRole } from '@/api/system/userRole';
import { getAdminRoleMenu } from '@/api/system/roleMenu';
import { uploadFile } from '@/api/common/upload';
import { alertMsg, getFileUrl } from '@/utils/system-helper';
import avatar from '@/assets/images/man.jpg';
import global from '@/constants/global';

export default {
    name: 'AdminUserView',
    data() {
        return {
            columns: [
                { field: 'adminId', title: '用户ID', align: 'center', width: '5%' },
                { field: 'name', title: '姓名', align: 'center', width: '5%' },
                { field: 'username', title: '账号', align: 'center', width: '5%' },
                {
                    field: 'avatar',
                    title: '头像',
                    align: 'center',
                    width: '5%',
                    formatter: function (val, row, index) {
                        let value = (val ? getFileUrl(val) : avatar);
                        return '<img src="' + value + '" width="70" height="60"/>';
                    }
                },
                {
                    field: 'status',
                    title: '状态',
                    align: 'center',
                    width: '5%',
                    formatter: function (val, row, index) {
                        return (val ? '有效' : '无效');
                    }
                },
                { field: 'email', title: '邮箱', align: 'center', width: '5%' },
                { field: 'phone', title: '手机号码', align: 'center', width: '5%' },
                {
                    field: 'createdAt',
                    title: '创建时间',
                    align: 'center',
                    width: '5%',
                    formatter: function (val, row, index) {
                        let value = '';
                        if (val) {
                            value = new Date(val).format("yyyy-MM-dd hh:mm:ss");
                        }
                        return value;
                    }
                },
                {
                    field: 'adminId',
                    title: '操作',
                    align: 'center',
                    width: '10%',
                    formatter: function (val, row, index) {
                        let value = '<button class="btn btn-sm btn-success opt-edit" data-id="' + val + '" data-index="' + index + '"><i class="glyphicon glyphicon-edit"></i>编辑</button>';
                        value += '<button class="btn btn-sm btn-warning opt-role" data-id="' + val + '" data-index="' + index + '"><i class="glyphicon glyphicon-user"></i>授权角色</button>';
                        value += '<button class="btn btn-sm btn-info opt-auth" data-id="' + val + '" data-index="' + index + '"><i class="glyphicon glyphicon-user"></i>查看授权</button>';
                        value += '<button class="btn btn-sm btn-primary opt-thaw" data-id="' + val + '" data-index="' + index + '"><i class="glyphicon glyphicon-remove"></i>解冻</button>';
                        value += '<button class="btn btn-sm btn-danger opt-del" data-id="' + val + '" data-index="' + index + '"><i class="glyphicon glyphicon-remove"></i>删除</button>';
                        return value;
                    }
                }
            ],
            adminUsers: [],
            adminUser: {
                adminId: 0,
                name: '',
                username: '',
                password: '',
                phone: '',
                avatar: '',
                email: '',
                introduction: '',
                status: 1,
            },
        };
    },
    created() {
        this.getUser();
        this.init();
    },
    methods: {
        imgFormatter(path) {
            return getFileUrl(path);
        },
        init() {
            this.$nextTick(function () {
                let $this = this;
                $this.initTable();
                $('#addBtn').click(function () {
                    $this.add();
                });
                $('#uploadBtn').click(function () {
                    uploadFile({
                        id: 'file',
                        bucketName: global.BUCKET.AVATAR
                    }).then(res => {
                        $this.adminUser.avatar = res.data.path;
                    });
                });
                $('#table').on('click', '.opt-edit', function () {
                    $this.edit(this);
                });
                $('#table').on('click', '.opt-role', function () {
                    $this.getUserRole(this);
                });
                $('#table').on('click', '.opt-auth', function () {
                    $this.authRoleMenu(this);
                });
                $('#table').on('click', '.opt-thaw', function () {
                    $this.thawUser(this);
                });
                $('#table').on('click', '.opt-del', function () {
                    $this.del(this);
                });
                $('#save').click(function () {
                    $this.save();
                });
                $('#roleSave').click(function () {
                    $this.saveUserRole();
                });
            });
        },
        initTable() {
            let $this = this;
            TableHelper.destroy('#table');
            $('#table').bootstrapTable({
                columns: $this.columns,
                cache: false,
                striped: true,
                showRefresh: true,
                search: true,
                pageSize: 10,
                pagination: true,
                pageList: [1, 10, 20, 30, 50],
                sidePagination: "client",
                queryParamsType: "undefined",
                toolbar: '#toolbar',
            });
        },
        getUser() {
            getAdminUserList().then(res => {
                this.adminUsers = res.data;
                TableHelper.load('#table', this.adminUsers);
            });
        },
        add() {
            this.adminUser.adminId = 0;
            this.adminUser.name = '';
            this.adminUser.username = '';
            this.adminUser.password = '';
            this.adminUser.phone = '';
            this.adminUser.avatar = '';
            this.adminUser.email = '';
            this.adminUser.introduction = '';
            this.adminUser.status = 1;
            this.show();
        },
        edit(obj) {
            const index = $(obj).data('index');
            const record = TableHelper.getData('#table')[index];
            this.adminUser.adminId = record.adminId;
            this.adminUser.name = record.name;
            this.adminUser.username = record.username;
            this.adminUser.password = '';
            this.adminUser.phone = record.phone;
            this.adminUser.avatar = record.avatar;
            this.adminUser.email = record.email;
            this.adminUser.introduction = record.introduction;
            this.adminUser.status = record.status;
            this.show();
        },
        show() {
            let uploadInfo = '未上传文件';
            if (this.adminUser.avatar) {
                uploadInfo = '已上传';
            }
            $('#uploadInfo').html(uploadInfo);
            ComboboxHelper.build('#status', this.adminUser.status);
            $('#userModal').modal('show');
        },
        save() {
            let data = this.adminUser;
            if (data.password) {
                data.password = $.md5(data.password);
            }
            data.status = ComboboxHelper.getSelected('#status');
            (data.adminId != 0 ? updateAdminUser(data) : saveAdminUser(data)).then(res => {
                console.log(res);
                this.getUser();
                $('#userModal').modal('hide');
            });

        },
        del(obj) {
            const index = $(obj).data('index');
            const record = TableHelper.getData('#table')[index];
            const adminId = record.adminId;
            if (!confirm('是否确定要删除?')) {
                return;
            }
            delAdminUser(adminId).then(res => {
                console.log(res);
                this.getUser();
            });
        },
        thawUser(obj) {
            const index = $(obj).data('index');
            const record = TableHelper.getData('#table')[index];
            const adminId = record.adminId;
            thawAdminUser(adminId).then(res => {
                alertMsg('解冻成功');
                this.getUser();
            });
        },
        getUserRole(obj) {
            const index = $(obj).data('index');
            const record = TableHelper.getData('#table')[index];
            this.adminUser.adminId = record.adminId;
            this.adminUser.name = record.name;
            const adminId = record.adminId;
            getAdminUserRole(adminId).then(res => {
                let adminRoles = res.data;
                let html = '';
                for (let i = 0, len = adminRoles.length; i < len; i++) {
                    let adminRole = adminRoles[i];
                    html += '<input type="checkbox" name="roles" value="' + adminRole.roleId + '" ' + (adminRole.checked ? 'checked' : '') + '/>' + adminRole.roleName + '&nbsp;&nbsp;&nbsp;';
                }
                $('#roles').html(html);
                $("#roleModal").modal('show');
            });
        },
        saveUserRole() {
            const adminId = this.adminUser.adminId;
            const roleArray = $("input:checkbox[name='roles']:checked").serializeArray();
            let roleIds = [];
            for (let i = 0, len = roleArray.length; i < len; i++) {
                console.log(roleArray[i]);
                roleIds[i] = roleArray[i].value;
            }
            saveAdminUserRole({
                adminId: adminId,
                roleIds: roleIds.join(',')
            });
            $("#roleModal").modal('hide');
        },
        async authRoleMenu(obj) {
            const index = $(obj).data('index');
            const record = TableHelper.getData('#table')[index];
            const adminId = record.adminId;
            getAdminUserRole(adminId).then(res => {
                let adminRoles = res.data;
                let promiseArray = [];
                for (let i = 0, len = adminRoles.length; i < len; i++) {
                    let adminRole = adminRoles[i];
                    promiseArray[i] = new Promise((resolve, reject) => {
                        getAdminRoleMenu(adminRole.roleId).then(res => {
                            resolve(res.data);
                        }).catch(() => {
                            reject();
                        });
                    });
                }
                Promise.all(promiseArray).then(item => {
                    let menus = [];
                    for (let i = 0; i < item.length; i++) {
                        let adminMenus = item[i];
                        if (menus.length == 0) {
                            for (let j = 0; j < adminMenus.length; j++) {
                                menus[j] = adminMenus[j];
                            }
                        } else {
                            for (let j = 0; j < menus.length; j++) {
                                let parentMenu = menus[j];
                                let adminMenu = adminMenus.filter(v => v.menuId == parentMenu.menuId)[0];
                                if (adminMenu.checked) {
                                    parentMenu.checked = true;
                                }
                                let childs = parentMenu.childs;
                                for (let k = 0; k < childs.length; k++) {
                                    let childMenu = childs[k];
                                    adminMenu = adminMenus.filter(v => v.menuId == parentMenu.menuId)[0];
                                    if (adminMenu.checked) {
                                        childMenu.checked = true;
                                    }
                                }
                            }
                        }
                    }
                    this.menuTree(menus);
                    $('#roleIds').val(adminRoles.map(v => v.roleId).join(','));
                    $('#roleNames').val(adminRoles.map(v => v.roleName).join(','));
                    $('#roleMenuModal').modal('show');
                });
            });
        },
        menuTree(menus) {
            let $this = this;
            let data = this.getMenuData(menus);
            console.log(data);
            $("#menuTree").treeview({
                data: data,// 数据源
                levels: 1,  //设置继承树默认展开的级别
                showTags: true, //是否在每个节点右边显示tags标签。tag值必须在每个列表树的data结构中给出
                showCheckbox: true,
                onNodeChecked: function (event, node) { //选中节点
                    var selectNodes = $this.getChildNodeIdArr(node); //获取所有子节点
                    if (selectNodes) { //子节点不为空,则选中所有子节点
                        $('#menuTree').treeview('checkNode', [selectNodes, { silent: true }]);
                    }
                    var parentNode = $("#menuTree").treeview("getNode", node.parentId);
                    if (parentNode) {
                        $('#menuTree').treeview('checkNode', [parentNode, { silent: true }]);
                    }
                },
                onNodeUnchecked: function (event, node) { //取消选中节点
                    var selectNodes = $this.getChildNodeIdArr(node); //获取所有子节点
                    if (selectNodes) { //子节点不为空,则取消选中所有子节点
                        $('#menuTree').treeview('uncheckNode', [selectNodes, { silent: true }]);
                    }
                }
            });
        },
        getMenuData(menus) {
            let parentMenus = [];
            for (let i = 0; i < menus.length; i++) {
                let parentMenu = menus[i];
                let childs = parentMenu.childs;
                let childMenus = [];
                if (childs && childs.length > 0) {
                    for (let j = 0; j < childs.length; j++) {
                        let childMenu = childs[j];
                        childMenus[j] = {
                            menuId: childMenu.menuId,
                            text: childMenu.menuName,
                            color: '#2f2424;display:inline-table;',
                            state: {
                                checked: childMenu.checked
                            }
                        };
                    }
                }
                parentMenus[i] = {
                    menuId: parentMenu.menuId,
                    text: parentMenu.menuName,
                    state: {
                        checked: parentMenu.checked,
                        expanded: parentMenu.checked,
                    },
                    nodes: childMenus
                };
            }
            return parentMenus;
        },
        getChildNodeIdArr(node) {
            var ts = [];
            if (node.nodes) {
                let x, j;
                for (x in node.nodes) {
                    ts.push(node.nodes[x].nodeId);
                    if (node.nodes[x].nodes) {
                        var getNodeDieDai = this.getChildNodeIdArr(node.nodes[x]);
                        for (j in getNodeDieDai) {
                            ts.push(getNodeDieDai[j]);
                        }
                    }
                }
            } else {
                ts.push(node.nodeId);
            }
            return ts;
        },
    },
}
</script>
  
<style scoped></style>

总结

剩下的功能全都放入git仓库中,怎么说的,本篇很水的!!!CRUD真不知道要讲啥好,大伙都会的活,只能当作代码分享吧。

照旧附上app的gitee地址(漫画/轻小说app):

https://gitee.com/liaz-app/liaz-android/releases/download/1.0.0/app-arm64-v8a-release.apk
  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值