golang结合neo4j实现权限功能设计

news2024/7/4 15:06:19

neo4j 是非关系型数据库之图形数据库,这里不再赘述。
传统关系数据库基于rbac实现权限, user ---- role ------permission,加上中间表共5张表。
如果再添上部门的概念:用户属于部门,部门拥有 角色,则又多了一层:
user
------
dept *-------- * role *------*permission,
如果再引入子部门概念。。。

1.权限设计

1.1 关系

user ---*-----> role --- * --> permission
user ------> dept [-->父dept -->父dept --->父dept]  ---可让子部门继承*-> role --- * --> permission
user ------> dept ---不允许子部门继承*-> role --- * --> permission

1.2 图

用户和部门之间的关系:
在这里插入图片描述

部门和子部门之间的关系:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

部门和角色关系:
在这里插入图片描述
角色和权限关系:
在这里插入图片描述
后台配置界面:
在这里插入图片描述

1.查询一个用户拥有的权限集:

match paths=(admin:Admin{name:'zs'})-[:HAS_ROLE]->(:Role)-[:HAS]->(p:Permission)
return p.id as id, p.name as name, p.url as url
union
match (admin:Admin{name:'zs'})
match paths=(admin)-[:BELONG_TO]->(:Dept)-[:CHILD_OF*0..3]->(d:Dept)-[:ALLOW_INHERIT]->(:Role)
-[:HAS]->(p:Permission)
return p.id as id, p.name as name, p.url as url
union
match (admin:Admin{name:'zs'})
match paths=(admin)-[:BELONG_TO]->(d:Dept)-[:ALLOW_NO_INHERIT]->(:Role)
-[:HAS]->(p:Permission)
return p.id as id, p.name as name, p.url as url

在这里插入图片描述
查询用户权限集:链路:

match paths=(admin:Admin{name:'xiaolan'})-[:HAS_ROLE]->(:Role)-[:HAS]->(p:Permission)
return paths
union
match (admin:Admin{name:'xiaolan'})
match paths=(admin)-[:BELONG_TO]->(:Dept)-[:CHILD_OF*0..3]->(d:Dept)-[:ALLOW_INHERIT]->(:Role)
-[:HAS]->(p:Permission)
return paths
union
match (admin:Admin{name:'xiaolan'})
match paths=(admin)-[:BELONG_TO]->(d:Dept)-[:ALLOW_NO_INHERIT]->(:Role)
-[:HAS]->(p:Permission)
return paths

在这里插入图片描述

2.判断一个用户是否可访问特定资源(url, 通过权限体现此概念):

match c=(admin:Admin{name:'xiaoQ'})-[:HAS_ROLE]->(:Role)-[:HAS]->(p:Permission{url:'/api/v2/goods/list'})
return count(c) as accessCount
union
match c=(admin:Admin{name:'xiaoQ'})-[:BELONG_TO]->(:Dept)-[:CHILD_OF*0..3]->(d:Dept)-[:ALLOW_INHERIT]->(:Role)
-[:HAS]->(p:Permission {url:'/api/v2/goods/list'})
where not ((admin)-[:BELONG_TO]->(:Dept)-[:CHILD_OF*0..3]->(d:Dept)-[:DENY]->(:Role))
return count(c) as accessCount
union
match c=(admin:Admin{name:'xiaoQ'})-[:BELONG_TO]->(d:Dept)-[:ALLOW_NO_INHERIT]->(:Role)
-[:HAS]->(p:Permission {url:'/api/v2/goods/list'})
return count(c) as accessCount

在这里插入图片描述

3.查看谁拥有指定资源(url) 的权限:

match (p:Permission{url:'/api/v2/admin/list'})
match (admin:Admin)-[:HAS_ROLE]->(:Role)-[:HAS]->(p)
return admin.id as id, admin.name as name
union
match (p:Permission{url:'/api/v2/admin/list'})
match (admin:Admin)-[:BELONG_TO]->(:Dept)-[:CHILD_OF*0..3]->(d:Dept)-[:ALLOW_INHERIT]->(:Role)-[:HAS]->(p)
where not ((admin)-[:BELONG_TO]->(:Dept)-[:CHILD_OF*0..3]->(d:Dept)-[:DENY]->(:Role))
return admin.id as id, admin.name as name
union
match (p:Permission{url:'/api/v2/admin/list'})
match (admin:Admin)-[:BELONG_TO]->(d:Dept)-[:ALLOW_NO_INHERIT]->(:Role)-[:HAS]->(p)
return admin.id as id, admin.name as name

在这里插入图片描述
附上完整关系图:
在这里插入图片描述

下面介绍golang代码整合处理:

先上成型图:
在这里插入图片描述
在这里插入图片描述

1.启动项目时读取配置,初始化neo4j driver:

package common

import (
	"context"
	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
	"log"
)

var DBName string
var Neo4jCtx = context.Background()
var Driver neo4j.DriverWithContext

func initNeo4jConfig(c neo4jConfig) {
	var err error
	// Driver is thread safe: can be shared by multiple threads
	Driver, err = neo4j.NewDriverWithContext(c.DbUri, neo4j.BasicAuth(c.DbUser, c.DbPwd, ""))
	if err != nil {
		log.Println("new neo4j driver with context failed:", err.Error())
		return
	}

	err = Driver.VerifyConnectivity(Neo4jCtx)
	if err != nil {
		log.Printf("init neo4j failed:%s\n", c)
		return
	}
	log.Println("neo4j connection established...")

	DBName = c.DBName
}

2.neo4j列表分页查询数据

func PageDept(pageNo, pageSize int, name string, queryParentOnly string, parent uint64) (*common.Page, error) {

	var ctx = common.Neo4jCtx
	session := common.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: common.DBName})
	defer session.Close(ctx)
	tx, err := session.BeginTransaction(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	whereSql, params := composeDeptSearchQuerySql(name, queryParentOnly, parent)

	res, err := tx.Run(ctx, whereSql+` return count(d.id) as c`, params)
	if err != nil {
		return nil, err
	}
	record, err := res.Single(ctx)
	if err != nil {
		return nil, err
	}
	var c = int64(0)
	if r, flg := record.Get("c"); flg && r != nil {
		c = r.(int64)
	}
	// 没有数据
	if c == int64(0) {
		return common.NewPage([]model.Dept{}, pageNo, pageSize, 0), nil
	}

	params["s"] = (pageNo - 1) * pageSize
	params["size"] = pageSize

	res, err = tx.Run(ctx, whereSql + ` return `+row+` order by d.id SKIP $s limit $size`, params)
	if err != nil {
		return nil, err
	}
	var ds []model.Dept

	for res.Next(ctx) {
		m := res.Record().AsMap()
		var d model.Dept
		err = mapstructure.Decode(m, &d)
		if err != nil {
			return nil, err
		}
		d.CreatedTimeStr = d.CreatedTime.Format(time.DateTime)
		ds = append(ds, d)
	}

	return common.NewPage(ds, pageNo, pageSize, int(c)), nil
}

func composeDeptSearchQuerySql(name string, only string, parent uint64) (string, map[string]any) {

	var params = map[string]any{}
	sb := strings.Builder{}
	sb.WriteString("match (d:Dept) ")

	// 没有条件查询
	if name == "" && only == "" && parent == 0 {
		return sb.String(), params
	}
	// 只查询父分类
	if only == "on" {
		sb.WriteString(" where d.parent = 0")
		return sb.String(), params
	}

	// 查询指定的父分类
	if parent > 0 {
		sb.WriteString("-[:CHILD_OF]->(:Dept{id: $parent})")
		//sb.WriteString(" where d.parent = $parent")
		params["parent"] = parent
	}

	// 有分类名称的模糊查询
	if len(name) > 0 {
		sb.WriteString(" where d.name CONTAINS $name")
		params["name"] = name
	}
	return sb.String(), params
}

权限permission dao for neo4j操作:

package neo

import (
	"commerce/common"
	"commerce/model"
	"fmt"
	"github.com/mitchellh/mapstructure"
	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
	"strings"
	"sync"
)

var permissionLock sync.Mutex

func PagePermission(pageNo, pageSize int, name string) (*common.Page, error) {

	var ctx = common.Neo4jCtx
	session := common.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: common.DBName})
	defer session.Close(ctx)
	tx, err := session.BeginTransaction(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	whereSql, params := composePermissionSearchQuerySql(name)

	res, err := tx.Run(ctx, whereSql+` return count(p.id) as c`, params)
	if err != nil {
		return nil, err
	}
	record, err := res.Single(ctx)
	if err != nil {
		return nil, err
	}
	var c = int64(0)
	if r, flg := record.Get("c"); flg && r != nil {
		c = r.(int64)
	}
	// 没有数据
	if c == int64(0) {
		return common.NewPage([]model.Permission{}, pageNo, pageSize, 0), nil
	}

	params["s"] = (pageNo - 1) * pageSize
	params["size"] = pageSize

	res, err = tx.Run(ctx, whereSql+` return p.id as id, p.name as name, p.priority as priority, p.status as status, p.public_res_flg as public_res_flg order by p.id SKIP $s limit $size`, params)
	if err != nil {
		return nil, err
	}
	var rs []model.Permission

	for res.Next(ctx) {
		m := res.Record().AsMap()
		var r model.Permission
		err = mapstructure.Decode(m, &r)
		if err != nil {
			return nil, err
		}
		rs = append(rs, r)
	}

	return common.NewPage(rs, pageNo, pageSize, int(c)), nil
}

func GetPermissionById(id uint64) (*model.Permission, error) {

	sqlTpl := `match (p:Permission {id: $id}) return p.id as id, p.name as name, p.priority as priority, p.status as status, p.public_res_flg as public_res_flg`

	res, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"id": id,
	}, neo4j.EagerResultTransformer,
		neo4j.ExecuteQueryWithDatabase(common.DBName),
		neo4j.ExecuteQueryWithReadersRouting(),
	)
	if err != nil {
		return nil, err
	}

	records := res.Records
	if records == nil || len(records) == 0 {
		return nil, err
	}

	m := records[0].AsMap()
	var r model.Permission
	err = mapstructure.Decode(m, &r)
	if err != nil {
		return nil, err
	}
	return &r, nil
}

func AddPermission(c model.Permission) (uint64, error) {

	lock.Lock()
	defer lock.Unlock()

	res, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, `match (p:Permission {name: $name}) return p.id as id limit 1`, map[string]any{"name": c.Name},
		neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName),
		neo4j.ExecuteQueryWithReadersRouting())

	if err != nil {
		return 0, err
	}
	if res.Records != nil && len(res.Records) > 0 {
		return 0, fmt.Errorf("%s 已存在,不允许创建", c.Name)
	}

	id := common.IdGenerator.Generate()

	sqlTpl := `create (p:Permission {id: $id, name: $name, priority: $priority, status:$status, 
		public_res_flg: $publicResFlg, created_time: datetime({timezone: 'Asia/Shanghai'}), 
		updated_time: datetime({timezone: 'Asia/Shanghai'})})`

	_, err = neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"id":           id,
		"name":         c.Name,
		"priority":     c.Priority,
		"status":       c.Status,
		"publicResFlg": c.PublicResFlg,
	}, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName))

	if err != nil {
		return 0, err
	}
	return id, nil
}

func UpdatePermission(c model.Permission) (bool, error) {

	lock.Lock()
	defer lock.Unlock()

	res, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, `match (p:Permission {name: $name}) return p.id as id limit 1`, map[string]any{"name": c.Name},
		neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName),
		neo4j.ExecuteQueryWithReadersRouting())

	if err != nil {
		return false, err
	}
	records := res.Records
	if records != nil && len(records) > 0 {
		r, _ := records[0].Get("id")
		dbId := uint64(r.(int64))

		if dbId != c.Id {
			return false, fmt.Errorf("%s 已存在,不允许更新部门名称为此值", c.Name)
		}
	}

	sqlTpl := `match (p:Permission {id: $id}) set p.name=$name, p.priority=$priority,
		p.public_res_flg=$publicResFlg, p.updated_time=datetime({timezone: 'Asia/Shanghai'})`

	res, err = neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"id":           c.Id,
		"name":         c.Name,
		"priority":     c.Priority,
		"publicResFlg": c.PublicResFlg,
	}, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName))

	if err != nil {
		return false, err
	}
	return res.Summary.Counters().ContainsUpdates(), nil
}

func composePermissionSearchQuerySql(name string) (string, map[string]any) {

	var params = map[string]any{}
	sb := strings.Builder{}
	sb.WriteString("match (p:Permission) ")

	// 没有条件查询
	if name == "" {
		return sb.String(), params
	}

	// 有分类名称的模糊查询
	if len(name) > 0 {
		sb.WriteString(" where p.name CONTAINS $name")
		params["name"] = name
	}
	return sb.String(), params
}

func UpdatePermissionStatus(id uint64, status int8) error {

	sqlTpl := `match (p:Permission {id: $id}) set p.status = $status, p.updated_time=datetime({timezone: 'Asia/Shanghai'})`

	_, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"id":     id,
		"status": status,
	}, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName))

	if err != nil {
		return err
	}

	return nil
}

func ListAllPermission() ([]model.Permission, error) {

	res, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver,
		`match (p:Permission) where p.status = $status return p.id as id, p.name as name order by p.priority`,
		map[string]any{"status": 1},
		neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName),
		neo4j.ExecuteQueryWithReadersRouting())

	if err != nil {
		return nil, err
	}
	records := res.Records
	var ps = make([]model.Permission, len(records))

	for i, r := range records {
		var p model.Permission
		err = mapstructure.Decode(r.AsMap(), &p)
		if err != nil {
			return nil, err
		}
		ps[i] = p
	}

	return ps, nil
}

func ListPermissionByRoleId(roleId uint64) ([]model.Permission, error) {

	res, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver,
		`match (p:Permission {status: $status})<-[:HAS]-(r:Role {id: $roleId}) 
				return distinct p.id as id, p.name as name`,
		map[string]any{"status": 1, "roleId": roleId},
		neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName),
		neo4j.ExecuteQueryWithReadersRouting())

	if err != nil {
		return nil, err
	}
	records := res.Records
	var ps = make([]model.Permission, len(records))

	for i, r := range records {
		var p model.Permission
		err = mapstructure.Decode(r.AsMap(), &p)
		if err != nil {
			return nil, err
		}
		ps[i] = p
	}

	return ps, nil
}

角色操作neo4j dao:

package neo

import (
	"commerce/common"
	"commerce/model"
	"fmt"
	"github.com/mitchellh/mapstructure"
	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
	"strings"
	"sync"
)

var roleLock sync.Mutex

func PageRole(pageNo, pageSize int, name string) (*common.Page, error) {

	var ctx = common.Neo4jCtx
	session := common.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: common.DBName})
	defer session.Close(ctx)
	tx, err := session.BeginTransaction(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	whereSql, params := composeRoleSearchQuerySql(name)

	res, err := tx.Run(ctx, whereSql+` return count(r.id) as c`, params)
	if err != nil {
		return nil, err
	}
	record, err := res.Single(ctx)
	if err != nil {
		return nil, err
	}
	var c = int64(0)
	if r, flg := record.Get("c"); flg && r != nil {
		c = r.(int64)
	}
	// 没有数据
	if c == int64(0) {
		return common.NewPage([]model.Role{}, pageNo, pageSize, 0), nil
	}

	params["s"] = (pageNo - 1) * pageSize
	params["size"] = pageSize

	res, err = tx.Run(ctx, whereSql+` return r.id as id, r.name as name, r.priority as priority, r.status as status order by r.id SKIP $s limit $size`, params)
	if err != nil {
		return nil, err
	}
	var rs []model.Role

	for res.Next(ctx) {
		m := res.Record().AsMap()
		var r model.Role
		err = mapstructure.Decode(m, &r)
		if err != nil {
			return nil, err
		}
		rs = append(rs, r)
	}

	return common.NewPage(rs, pageNo, pageSize, int(c)), nil
}

func GetRoleById(id uint64) (*model.Role, error) {

	sqlTpl := `match (r:Role {id: $id}) return r.id as id, r.name as name, r.priority as priority, r.status as status`

	res, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"id": id,
	}, neo4j.EagerResultTransformer,
		neo4j.ExecuteQueryWithDatabase(common.DBName),
		neo4j.ExecuteQueryWithReadersRouting(),
	)
	if err != nil {
		return nil, err
	}

	records := res.Records
	if records == nil || len(records) == 0 {
		return nil, err
	}

	m := records[0].AsMap()
	var r model.Role
	err = mapstructure.Decode(m, &r)
	if err != nil {
		return nil, err
	}
	return &r, nil
}

func AddRole(c model.Role) (uint64, error) {

	roleLock.Lock()
	defer roleLock.Unlock()

	res, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, `match (r:Role {name: $name}) return r.id as id limit 1`, map[string]any{"name": c.Name},
		neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName),
		neo4j.ExecuteQueryWithReadersRouting())

	if err != nil {
		return 0, err
	}
	if res.Records != nil && len(res.Records) > 0 {
		return 0, fmt.Errorf("%s 已存在,不允许创建", c.Name)
	}

	id := common.IdGenerator.Generate()

	sqlTpl := `create (r:Role {id: $id, name: $name,
		priority: $priority, status:$status, created_time: datetime({timezone: 'Asia/Shanghai'}), 
		updated_time: datetime({timezone: 'Asia/Shanghai'})})`

	_, err = neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"id":       id,
		"name":     c.Name,
		"priority": c.Priority,
		"status":   c.Status,
	}, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName))

	if err != nil {
		return 0, err
	}
	return id, nil
}

func UpdateRole(c model.Role) (bool, error) {

	roleLock.Lock()
	defer roleLock.Unlock()

	res, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, `match (r:Role {name: $name}) return r.id as id limit 1`, map[string]any{"name": c.Name},
		neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName),
		neo4j.ExecuteQueryWithReadersRouting())

	if err != nil {
		return false, err
	}
	records := res.Records
	if records != nil && len(records) > 0 {
		r, _ := records[0].Get("id")
		dbId := uint64(r.(int64))

		if dbId != c.Id {
			return false, fmt.Errorf("%s 已存在,不允许更新部门名称为此值", c.Name)
		}
	}

	sqlTpl := `match (r:Role {id: $id}) set r.name=$name, r.priority=$priority, r.updated_time=datetime({timezone: 'Asia/Shanghai'})`

	res, err = neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"id":       c.Id,
		"name":     c.Name,
		"priority": c.Priority,
	}, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName))

	if err != nil {
		return false, err
	}
	return res.Summary.Counters().ContainsUpdates(), nil
}

func composeRoleSearchQuerySql(name string) (string, map[string]any) {

	var params = map[string]any{}
	sb := strings.Builder{}
	sb.WriteString("match (r:Role) ")

	// 没有条件查询
	if name == "" {
		return sb.String(), params
	}

	// 有分类名称的模糊查询
	if len(name) > 0 {
		sb.WriteString(" where r.name CONTAINS $name")
		params["name"] = name
	}
	return sb.String(), params
}

func UpdateRoleStatus(id uint64, status int8) error {

	sqlTpl := `match (r:Role {id: $id}) set r.status = $status, r.updated_time=datetime({timezone: 'Asia/Shanghai'})`

	_, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"id":     id,
		"status": status,
	}, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName))

	if err != nil {
		return err
	}

	return nil
}

func AttachRolePermissionList(roleId uint64, permissionIdList []uint64) error {

	var sqlTpl string
	if len(permissionIdList) == 0 {
		sqlTpl = `match (:Role {id: $roleId})-[rel:HAS]->(:Permission) delete rel`
	} else {
		sqlTpl = `match (r:Role {id: $roleId})
				CALL {match (r:Role {id: $roleId})-[rel:HAS]->(:Permission) delete rel }
				with r
				unwind $permissionIdList as pId
				match (p:Permission {id: pId})
				merge (r)-[:HAS]->(p)`
	}
	_, err := neo4j.ExecuteQuery(common.Neo4jCtx, common.Driver, sqlTpl, map[string]any{
		"roleId":           roleId,
		"permissionIdList": permissionIdList,
	}, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase(common.DBName))

	if err != nil {
		return err
	}

	return nil
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1886567.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vulnhub靶场ai-web 2.0

1 信息收集 1.1 主机发现 arp-scan -l 主机地址为192.168.1.4 1.2 服务端口扫描 nmap -sS -sV -A -T5 -p- 192.168.1.4 开放22&#xff0c;80端口 2 访问服务 2.1 80端口访问 http://192.168.1.4:80/ 先尝试admin等其他常见用户名登录无果 然后点击signup发现这是一个注…

Codeforces Round 346 (Div. 2) E. New Reform 题解 并查集

New Reform 题目描述 Berland has n n n cities connected by m m m bidirectional roads. No road connects a city to itself, and each pair of cities is connected by no more than one road. It is not guaranteed that you can get from any city to any other one,…

如何养成爱自己的习惯:吸引世间美好,改变命运

在这个快节奏、高压力的时代&#xff0c;我们常常被各种事务所困扰&#xff0c;内心难以得到真正的宁静。然而&#xff0c;古老的智慧告诉我们&#xff0c;“静”是宇宙万物的根源&#xff0c;是生命恢复的根本。本文将探讨如何养成“静”的习惯&#xff0c;从而吸引世间美好&a…

Apache POI、EasyPoi、EasyExcel

&#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&#xff09;EasyPoi使用 &#xff08;三&#xff09;EasyExcel使用 官方文档&#xff1a; 写Excel | Easy Excel 官网 写 使用注解在字段上进行标识 使用最简单的方法二即可 /*** 最简单的写* <p>* 1. 创建…

入门PHP就来我这(纯干货)04

~~~~ 有胆量你就来跟着路老师卷起来&#xff01; -- 纯干货&#xff0c;技术知识分享 ~~~~ 路老师给大家分享PHP语言的知识了&#xff0c;旨在想让大家入门PHP&#xff0c;并深入了解PHP语言。 我们接着《想入门PHP就来我这&#xff08;纯干货&#xff09;03》继续往下学习&am…

忘记家里的wifi密码用iPhone苹果手机怎么找回?

忘记家里的wifi密码用iPhone苹果手机怎么找回&#xff1f; 1、打开iPhone苹果手机上的设置&#xff1b; 2、在iPhone苹果手机设置里找到并进入无线局域网&#xff1b; 3、选择要找回密码的wifi&#xff0c;且已连接&#xff0c;并点击后面的更多进入&#xff1b; 4、进入无线局…

Excel分组求和

目录 1 参考文章2 UNIQUE函数分组3 SUMIF函数分组求和 1 参考文章 1.整体思路&#xff1a;https://blog.csdn.net/Alice_loong/article/details/135580130 2.UNIQUE函数&#xff1a;https://mp.weixin.qq.com/s?__bizMzI3OTcwNDE3OQ&mid2247487044&idx1&sna28108…

浅谈Web性能测试(原创)

一、性能测试不是什么高技术的活&#xff1a; 说到性能测试&#xff0c;很多工作时间较短的新同事或者应届生就很害怕。 为什么害怕&#xff0c;因为感觉无从下手&#xff0c;不知道该做什么、怎么做、做到什么程度&#xff1f; 一听性能测试首先想到的是各种专业的性能测试…

Echarts-柱状图

1.案例1 1.1代码 option = {textStyle: {color: #fff // 标题文字颜色为白色},tooltip: {trigger: axis,axisPointer: {type: shadow,},},legend: {textStyle: {color: white}},grid: {top: 15%,left: 4%,right: 4%,bottom: 7%,containLabel: true},xAxis:{type: category,da…

分文件编译(简单学生系统)

定义学生基本信息 ①输出所有学生信息 ②删除某个学生后&#xff0c;输出所有学生信息 ③修改某个学生信息后&#xff0c;输出所有学生信息 ④查找某个学生的信息 main.c #include"k11*.h" int main(int argc, const char *argv[]) {struct student p[4]{{"…

ShareX:不仅仅是截图工具

名人说&#xff1a;莫道谗言如浪深&#xff0c;莫言迁客似沙沉。 ——刘禹锡《浪淘沙》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、软件介绍1、ShareX2、核心功能 二、下载安装1、下载2、安装 三、使用方法…

CentOS7.9下yum升级Apache HTTP Server2.4.6到2.4.60

CentOS7.9系统默认的Apache版本 在CentOS7.9上&#xff0c;如果使用yum安装Apache HTTP Server是最多到2.4.6版本的&#xff0c;这是因为el7下官方仓库的最高版本就是2.4.6&#xff0c;证据如下&#xff1a; $ yum info httpd ...... Installed Packages Name : httpd…

虚拟纪念展馆建设的重大意义:重新定义纪念活动的未来

一、什么是虚拟纪念展馆&#xff1f; 虚拟纪念展馆是一种利用3D、VR等技术在线展示历史事件、人物或文化遗产的数字化空间。这些展馆通过虚拟现实、增强现实和3D建模等技术手段&#xff0c;创建出身临其境的体验&#xff0c;使参观者可以在互联网上以互动方式探索和学习。 二、…

Golang | Leetcode Golang题解之第205题同构字符串

题目&#xff1a; 题解&#xff1a; func isIsomorphic(s, t string) bool {s2t : map[byte]byte{}t2s : map[byte]byte{}for i : range s {x, y : s[i], t[i]if s2t[x] > 0 && s2t[x] ! y || t2s[y] > 0 && t2s[y] ! x {return false}s2t[x] yt2s[y] …

nginx 只有图片等静态资源时 监听80端口 会404 NOT FOUND

解决方法 删除 /var/nginx/sites-enabled 原因&#xff1a;当nginx没有设置首页路径index时&#xff0c;sites-enabled目录中配置的优先级会高于nginx.conf 导致404 NOT FOUND sites-enabled文件中的default会将80端口索引至默认值&#xff1a;/var/www/html目录下&#xff…

Redis基础教程(八):redis集合(Set)

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

职场小白必备待办工具有哪些 适合新手的待办app

初入职场的小白们&#xff0c;常常会遇到各种挑战。从最初的迷茫&#xff0c;到对工作的逐步熟悉&#xff0c;每一步都需要时间和精力的投入。尤其是当面对繁多的工作任务时&#xff0c;如何快速有效地完成它们&#xff0c;成为了许多职场新人需要面对的问题。 在这个快节奏的…

数字集群手持终端是什么_鼎跃安全

在当今快速发展的科技时代&#xff0c;通信技术的进步为各行各业带来了巨大的变革。尤其是在公共安全、应急救援和交通运输等领域&#xff0c;通信的及时性和可靠性变得尤为重要。数字集群手持终端作为一种专用于数字集群通信系统的便携式设备&#xff0c;数字集群手持终端是一…

AI模特换装试衣软件定制服务公司

&#x1f31f; 最强AI模特换装试衣模型训练、定制服务公司出炉 —— 触站AI&#x1f680; &#x1f3a8; 在AI技术的浪潮中&#xff0c;触站AI以其专业和创新&#xff0c;成为企业AI图像领域的技术解决方案服务公司&#xff0c;为设计界带来了革命性的变化。 &#x1f6e0;️ …

PDF一键转PPT文件!这2个AI工具值得推荐,办公必备!

PDF转换为PPT文件&#xff0c;是职场上非常常见的需求&#xff0c;过去想要把PDF文件转换为PPT&#xff0c;得借助各种文件转换工具&#xff0c;但在如今AI技术主导的大背景下&#xff0c;我们在选用工具时有了更多的选择&#xff0c;最明显的就是基于AI技术打造的AI格式转换工…