mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-01 18:40:18 +08:00
feat: add group xlsx upload button (#3885)
This commit is contained in:
56
controllers/group_upload.go
Normal file
56
controllers/group_upload.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) UploadGroups() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||||
|
|
||||||
|
file, header, err := c.Ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
defer os.Remove(path)
|
||||||
|
|
||||||
|
err = saveFile(path, &file)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.UploadGroups(owner, path)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if affected {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("group_upload:Failed to import groups"))
|
||||||
|
}
|
||||||
|
}
|
@ -181,6 +181,41 @@ func AddGroups(groups []*Group) (bool, error) {
|
|||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddGroupsInBatch(groups []*Group) (bool, error) {
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
session := ormer.Engine.NewSession()
|
||||||
|
defer session.Close()
|
||||||
|
err := session.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
err = checkGroupName(group.Name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := session.Insert(group)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if affected == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func deleteGroup(group *Group) (bool, error) {
|
func deleteGroup(group *Group) (bool, error) {
|
||||||
affected, err := ormer.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
|
affected, err := ormer.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
61
object/group_upload.go
Normal file
61
object/group_upload.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/xlsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getGroupMap(owner string) (map[string]*Group, error) {
|
||||||
|
m := map[string]*Group{}
|
||||||
|
|
||||||
|
groups, err := GetGroups(owner)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
m[group.GetId()] = group
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadGroups(owner string, path string) (bool, error) {
|
||||||
|
table := xlsx.ReadXlsxFile(path)
|
||||||
|
|
||||||
|
oldGroupMap, err := getGroupMap(owner)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transGroups, err := StringArrayToStruct[Group](table)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newGroups := []*Group{}
|
||||||
|
for _, group := range transGroups {
|
||||||
|
if _, ok := oldGroupMap[group.GetId()]; !ok {
|
||||||
|
newGroups = append(newGroups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newGroups) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddGroupsInBatch(newGroups)
|
||||||
|
}
|
@ -81,7 +81,7 @@ func UploadUsers(owner string, path string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
transUsers, err := StringArrayToUser(table)
|
transUsers, err := StringArrayToStruct[User](table)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -724,14 +724,14 @@ func setReflectAttr[T any](fieldValue *reflect.Value, fieldString string) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StringArrayToUser(stringArray [][]string) ([]*User, error) {
|
func StringArrayToStruct[T any](stringArray [][]string) ([]*T, error) {
|
||||||
fieldNames := stringArray[0]
|
fieldNames := stringArray[0]
|
||||||
excelMap := []map[string]string{}
|
excelMap := []map[string]string{}
|
||||||
userFieldMap := map[string]int{}
|
structFieldMap := map[string]int{}
|
||||||
|
|
||||||
reflectedUser := reflect.TypeOf(User{})
|
reflectedStruct := reflect.TypeOf(*new(T))
|
||||||
for i := 0; i < reflectedUser.NumField(); i++ {
|
for i := 0; i < reflectedStruct.NumField(); i++ {
|
||||||
userFieldMap[strings.ToLower(reflectedUser.Field(i).Name)] = i
|
structFieldMap[strings.ToLower(reflectedStruct.Field(i).Name)] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, field := range stringArray {
|
for idx, field := range stringArray {
|
||||||
@ -746,22 +746,23 @@ func StringArrayToUser(stringArray [][]string) ([]*User, error) {
|
|||||||
excelMap = append(excelMap, tempMap)
|
excelMap = append(excelMap, tempMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
users := []*User{}
|
instances := []*T{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
for _, u := range excelMap {
|
for _, m := range excelMap {
|
||||||
user := User{}
|
instance := new(T)
|
||||||
reflectedUser := reflect.ValueOf(&user).Elem()
|
reflectedInstance := reflect.ValueOf(instance).Elem()
|
||||||
for k, v := range u {
|
|
||||||
|
for k, v := range m {
|
||||||
if v == "" || v == "null" || v == "[]" || v == "{}" {
|
if v == "" || v == "null" || v == "[]" || v == "{}" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fName := strings.ToLower(strings.ReplaceAll(k, "_", ""))
|
fName := strings.ToLower(strings.ReplaceAll(k, "_", ""))
|
||||||
fieldIdx, ok := userFieldMap[fName]
|
fieldIdx, ok := structFieldMap[fName]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fv := reflectedUser.Field(fieldIdx)
|
fv := reflectedInstance.Field(fieldIdx)
|
||||||
if !fv.IsValid() {
|
if !fv.IsValid() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -806,8 +807,8 @@ func StringArrayToUser(stringArray [][]string) ([]*User, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
users = append(users, &user)
|
instances = append(instances, instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return instances, nil
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
|
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
|
||||||
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
|
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
|
||||||
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
|
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
|
||||||
|
beego.Router("/api/upload-groups", &controllers.ApiController{}, "POST:UploadGroups")
|
||||||
|
|
||||||
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
|
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
|
||||||
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
|
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Table, Tooltip} from "antd";
|
import {Button, Table, Tooltip, Upload} from "antd";
|
||||||
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as GroupBackend from "./backend/GroupBackend";
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
@ -87,6 +88,42 @@ class GroupListPage extends BaseListPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadFile(info) {
|
||||||
|
const {status, response: res} = info.file;
|
||||||
|
if (status === "done") {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", "Groups uploaded successfully, refreshing the page");
|
||||||
|
const {pagination} = this.state;
|
||||||
|
this.fetch({pagination});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Groups failed to upload: ${res.msg}`);
|
||||||
|
}
|
||||||
|
} else if (status === "error") {
|
||||||
|
Setting.showMessage("error", "File failed to upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUpload() {
|
||||||
|
const props = {
|
||||||
|
name: "file",
|
||||||
|
accept: ".xlsx",
|
||||||
|
method: "post",
|
||||||
|
action: `${Setting.ServerUrl}/api/upload-groups`,
|
||||||
|
withCredentials: true,
|
||||||
|
onChange: (info) => {
|
||||||
|
this.uploadFile(info);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Upload {...props}>
|
||||||
|
<Button icon={<UploadOutlined />} id="upload-button" type="primary" size="small">
|
||||||
|
{i18next.t("group:Upload (.xlsx)")}
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderTable(data) {
|
renderTable(data) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -231,7 +268,10 @@ class GroupListPage extends BaseListPage {
|
|||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Groups")}
|
{i18next.t("general:Groups")}
|
||||||
<Button type="primary" size="small" onClick={this.addGroup.bind(this)}>{i18next.t("general:Add")}</Button>
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addGroup.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
{
|
||||||
|
this.renderUpload()
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
|
BIN
xlsx/group_test.xlsx
Normal file
BIN
xlsx/group_test.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user