chore: 初始开源发布

iotlcm-app:小程序端 + 用户端服务。

- app/:uni-app 小程序前端(基于 unibest,MIT)
  - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID
  - 内网后端地址 → admin.iotlcm.example.com
  - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明)
- server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0)
  - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件)
  - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化
  - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位)
  - 依赖号码尾部的断言同步更新
  - 补 server/LICENSE(Apache-2.0)
- 移除内部资产:CI 触发器
- 补根 README,LF 行尾 + .gitattributes

已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
This commit is contained in:
MiFaZhan
2026-10-26 14:01:25 +08:00
commit 2ed5dfa6a1
1013 changed files with 138466 additions and 0 deletions
@@ -0,0 +1,224 @@
// package handler
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <li@xiujie.cn>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package handler
import (
"context"
"fmt"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-app/utility/convert"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
// FilterAuth 过滤数据权限
// 通过上下文中的用户角色权限和表中是否含有需要过滤的字段附加查询条件
func FilterAuth(m *gdb.Model) *gdb.Model {
var (
ctx = m.GetCtx()
fields = convert.EscapeFieldsToSlice(m.GetFieldsStr())
)
// 超管拥有全部权限
if contexts.IsSuperAdmin(ctx) {
return m
}
// 获取用户信息
user := contexts.GetUser(ctx)
if user == nil {
return m
}
if len(user.Roles) == 0 {
return m
}
deptIds := make([]int64, 0)
userIds := make([]int64, 0)
// 是否进入了非「全部数据」的受限范围;解析结果为空时必须拒绝,不能漏过滤。
hasRestrictedScope := false
currentRoleId := ""
currentRoleDataScope := ""
currentRoleIdIf := contexts.GetDataValue(ctx, "currentRoleId")
if currentRoleIdIf != nil {
currentRoleId = currentRoleIdIf.(string)
}
currentRoleDataScopeIf := contexts.GetDataValue(ctx, "currentRoleDataScope")
if currentRoleDataScopeIf != nil {
currentRoleDataScope = currentRoleDataScopeIf.(string)
}
if len(currentRoleDataScope) > 0 && len(currentRoleId) > 0 {
dataScope := currentRoleDataScope
roleId := gconv.Int64(currentRoleId)
g.Log().Infof(ctx, "uid:%d, currentRoleId: %d, currentRoleDataScope: %s", user.ID, roleId, dataScope)
if dataScope == consts.SysRoleDataScopeAll { // 角色数据范围: 1全部数据权限
return m
}
hasRestrictedScope = true
if dataScope == consts.SysRoleDataScopeCustom { // 角色数据范围: 2自定数据权限
var roleDepts []*entity.SysRoleDept
err := g.Model("sys_role_dept").Where("tenant_id", contexts.GetTenantId(ctx)).Where("role_id", roleId).Scan(&roleDepts)
if err != nil {
g.Log().Panicf(ctx, "failed to get role dept data err:%+v", err)
}
for _, roleDept := range roleDepts {
deptIds = append(deptIds, roleDept.DeptId)
}
} else if dataScope == consts.SysRoleDataScopeDept { // 角色数据范围: 3本部门数据权限
deptIds = append(deptIds, user.DeptId)
} else if dataScope == consts.SysRoleDataScopeDeptAndBelow { // 角色数据范围: 4本部门及以下数据权限
deptIds = append(deptIds, GetDeptAndSub(ctx, user.DeptId)...)
} else if dataScope == consts.SysRoleDataScopePersonal { // 角色数据范围: 5仅本人数据权限
userIds = append(userIds, user.ID)
} else if dataScope == consts.SysRoleDataScopeDeptAndBelowOrPersonal { // 角色数据范围: 6本部门及以下或本人数据权限
deptIds = append(deptIds, GetDeptAndSub(ctx, user.DeptId)...)
userIds = append(userIds, user.ID)
} else if dataScope == consts.SysRoleDataScopeOrgAndNextLevel { // 角色数据范围: 7本组织及本组织下一级数据权限
deptIds = append(deptIds, GetOrgAndNextLevelDeptIds(ctx, user.DeptId)...)
} else if dataScope == consts.SysRoleDataScopeOrgNextLevel { // 角色数据范围: 8本组织下一级数据权限
deptIds = append(deptIds, GetOrgNextLevelDeptIds(ctx, user.DeptId)...)
}
} else {
for _, role := range user.Roles {
dataScope := role.DataScope
roleId := role.RoleId
if dataScope == consts.SysRoleDataScopeAll { // 角色数据范围: 1全部数据权限
return m
} else if dataScope == consts.SysRoleDataScopeCustom { // 角色数据范围: 2自定数据权限
hasRestrictedScope = true
var roleDepts []*entity.SysRoleDept
err := g.Model("sys_role_dept").Where("tenant_id", contexts.GetTenantId(ctx)).Where("role_id", roleId).Scan(&roleDepts)
if err != nil {
g.Log().Panicf(ctx, "failed to get role dept data err:%+v", err)
}
for _, roleDept := range roleDepts {
deptIds = append(deptIds, roleDept.DeptId)
}
} else if dataScope == consts.SysRoleDataScopeDept { // 角色数据范围: 3本部门数据权限
hasRestrictedScope = true
deptIds = append(deptIds, user.DeptId)
} else if dataScope == consts.SysRoleDataScopeDeptAndBelow { // 角色数据范围: 4本部门及以下数据权限
hasRestrictedScope = true
deptIds = append(deptIds, GetDeptAndSub(ctx, user.DeptId)...)
} else if dataScope == consts.SysRoleDataScopePersonal { // 角色数据范围: 5仅本人数据权限
hasRestrictedScope = true
userIds = append(userIds, user.ID)
} else if dataScope == consts.SysRoleDataScopeDeptAndBelowOrPersonal { // 角色数据范围: 6本部门及以下或本人数据权限
hasRestrictedScope = true
deptIds = append(deptIds, GetDeptAndSub(ctx, user.DeptId)...)
userIds = append(userIds, user.ID)
} else if dataScope == consts.SysRoleDataScopeOrgAndNextLevel { // 角色数据范围: 7本组织及本组织下一级数据权限
hasRestrictedScope = true
deptIds = append(deptIds, GetOrgAndNextLevelDeptIds(ctx, user.DeptId)...)
} else if dataScope == consts.SysRoleDataScopeOrgNextLevel { // 角色数据范围: 8本组织下一级数据权限
hasRestrictedScope = true
deptIds = append(deptIds, GetOrgNextLevelDeptIds(ctx, user.DeptId)...)
}
}
}
deptFilterField := ""
if gstr.InArray(fields, "dept_id") {
deptFilterField = "dept_id"
} else if gstr.InArray(fields, "created_dept") {
deptFilterField = "created_dept"
}
userFilterField := ""
if gstr.InArray(fields, "user_id") {
userFilterField = "user_id"
} else if gstr.InArray(fields, "created_by") {
userFilterField = "created_by"
}
if len(deptIds) > 0 && len(userIds) > 0 {
if deptFilterField != "" && userFilterField != "" {
m = m.Where(deptFilterField+" IN (?) OR "+userFilterField+" IN (?)", deptIds, userIds)
} else if deptFilterField != "" {
m = m.WhereIn(deptFilterField, deptIds)
} else if userFilterField != "" {
m = m.WhereIn(userFilterField, userIds)
}
} else if len(deptIds) > 0 && deptFilterField != "" {
m = m.WhereIn(deptFilterField, deptIds)
} else if len(userIds) > 0 && userFilterField != "" {
m = m.WhereIn(userFilterField, userIds)
} else if hasRestrictedScope && (deptFilterField != "" || userFilterField != "") {
// 例如:范围8无下级部门、范围2未配自定义部门 -> 可见集合为空,禁止放行全表。
g.Log().Warningf(ctx, "FilterAuth empty scope result, deny all data. uid:%d, deptIds:%v, userIds:%v", user.ID, deptIds, userIds)
m = m.Where("1 = 0")
}
return m
}
// GetParentCompanyDeptId 获取用户所属上级公司组织ID
func GetParentCompanyDeptId(ctx context.Context, deptId int64) int64 {
if deptId <= 0 {
return deptId
}
var dept entity.SysDept
err := g.Model("sys_dept").Where("dept_id", deptId).Scan(&dept)
if err != nil || dept.DeptId == 0 {
return deptId
}
if dept.ParentId == 0 || dept.DeptType == 1 {
return dept.DeptId
}
return GetParentCompanyDeptId(ctx, dept.ParentId)
}
// GetDeptDirectSub 获取指定部门的直接下级部门ID
func GetDeptDirectSub(ctx context.Context, deptId int64) (ids []int64) {
array, err := g.Model("sys_dept").
Where("parent_id", deptId).
Fields("dept_id").
Array()
if err != nil {
g.Log().Panicf(ctx, "GetDeptDirectSub err:%+v", err)
return
}
for _, v := range array {
ids = append(ids, v.Int64())
}
return
}
// GetOrgAndNextLevelDeptIds 获取本组织及本组织下一级数据权限范围:所属公司组织及直接下级
func GetOrgAndNextLevelDeptIds(ctx context.Context, deptId int64) (ids []int64) {
orgDeptId := GetParentCompanyDeptId(ctx, deptId)
ids = append(ids, orgDeptId)
ids = append(ids, GetDeptDirectSub(ctx, orgDeptId)...)
return
}
// GetOrgNextLevelDeptIds 获取本组织下一级数据权限范围:所属公司组织的直接下级
func GetOrgNextLevelDeptIds(ctx context.Context, deptId int64) (ids []int64) {
orgDeptId := GetParentCompanyDeptId(ctx, deptId)
return GetDeptDirectSub(ctx, orgDeptId)
}
// GetDeptAndSub 获取指定部门的所有下级,含本部门
func GetDeptAndSub(ctx context.Context, deptId int64) (ids []int64) {
array, err := g.Model("sys_dept").
WhereLike("ancestors", "%,"+fmt.Sprintf("%d", deptId)+",%").
Fields("dept_id").
Array()
if err != nil {
g.Log().Panicf(ctx, "GetDeptAndSub err:%+v", err)
return
}
for _, v := range array {
ids = append(ids, v.Int64())
}
ids = append(ids, deptId)
return
}
@@ -0,0 +1,13 @@
// package handler
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <li@xiujie.cn>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package handler
import "github.com/gogf/gf/v2/database/gdb"
// ForceCache 强制缓存
func ForceCache(m *gdb.Model) *gdb.Model {
return m.Cache(gdb.CacheOption{Duration: 0, Force: true})
}
@@ -0,0 +1,40 @@
// package handler
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <li@xiujie.cn>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package handler
import "github.com/gogf/gf/v2/database/gdb"
// Option 预处理选项
type Option struct {
FilterAuth bool // 过滤权限
ForceCache bool // 强制缓存
FilterTenant bool // 过滤多租户权限
}
// DefaultOption 默认预处理选项
var DefaultOption = &Option{
FilterAuth: true,
FilterTenant: true,
}
func Model(m *gdb.Model, opt ...*Option) *gdb.Model {
var option *Option
if len(opt) > 0 {
option = opt[0]
} else {
option = DefaultOption
}
if option.FilterAuth {
m = m.Handler(FilterAuth)
}
if option.ForceCache {
m = m.Handler(ForceCache)
}
if option.FilterTenant {
m = m.Handler(FilterTenant)
}
return m
}
@@ -0,0 +1,27 @@
// package handler
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <li@xiujie.cn>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package handler
import (
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-app/utility/convert"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/text/gstr"
)
// FilterTenant 过滤多租户数据权限
func FilterTenant(m *gdb.Model) *gdb.Model {
var (
ctx = m.GetCtx()
fields = convert.EscapeFieldsToSlice(m.GetFieldsStr())
)
// 如果包含tenant_id字段,则过滤多租户数据权限
if gstr.InArray(fields, "tenant_id") {
m = m.Where("tenant_id", contexts.GetTenantId(ctx))
}
return m
}