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:
@@ -0,0 +1,393 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"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/library/mcache"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type appFeatureAccessRule struct {
|
||||
Feature string
|
||||
Permissions []string
|
||||
Routes []string
|
||||
}
|
||||
|
||||
// appFeatureAccessRules maps mini-program business features to existing console
|
||||
// menu permissions in sys_menu.perms. It deliberately avoids role names so the
|
||||
// app follows the same role-menu binding model as the admin console.
|
||||
var appFeatureAccessRules = []appFeatureAccessRule{
|
||||
{
|
||||
Feature: "card:list",
|
||||
Permissions: []string{"cpa:terminal:user", "cpm:iot:tSimCardInfo:list"},
|
||||
Routes: []string{"/pages/card/index"},
|
||||
},
|
||||
{
|
||||
Feature: "card:detail",
|
||||
Permissions: []string{"cpm:iot:tSimCardInfo:view", "cpm:iot:tSimCardInfo:list"},
|
||||
Routes: []string{"/pages/card/views/detail/index"},
|
||||
},
|
||||
{
|
||||
Feature: "package:view",
|
||||
Permissions: []string{
|
||||
"cpm:iot:tSimCardBindPkg:list",
|
||||
"cpm:iot:tSimFlowPackageInfo:list",
|
||||
"cpm:iot:tSimFlowPackageInfo:view",
|
||||
"cpm:iot:tSimCardBindPkg:getPkgInfo",
|
||||
},
|
||||
Routes: []string{"/pages/card/views/packageDetail/index"},
|
||||
},
|
||||
{
|
||||
Feature: "card:bind",
|
||||
Permissions: []string{consts.SysCheckPermissionCurrentUser},
|
||||
},
|
||||
{
|
||||
Feature: "card:renew",
|
||||
Permissions: []string{"cpm:iot:tSimCardRenew:batch:confirm"},
|
||||
Routes: []string{
|
||||
"/pages/business/views/renew/index",
|
||||
"/pages/business/views/cardRenew/index",
|
||||
},
|
||||
},
|
||||
{
|
||||
Feature: "card:renew:submit",
|
||||
Permissions: []string{"cpm:iot:tSimCardRenew:batch:confirm"},
|
||||
},
|
||||
{
|
||||
Feature: "card:addon",
|
||||
Permissions: []string{
|
||||
"cpm:iot:tSimCardBindPkg:list",
|
||||
"cpm:iot:tSimFlowPackageInfo:view",
|
||||
"cpm:iot:tSimCardBindPkg:getPkgInfo",
|
||||
},
|
||||
Routes: []string{"/pages/business/views/addon/index"},
|
||||
},
|
||||
{
|
||||
Feature: "card:change",
|
||||
Permissions: []string{"cpm:iot:tSimCardChange:batch:submit"},
|
||||
Routes: []string{"/pages/business/views/change/index"},
|
||||
},
|
||||
{
|
||||
Feature: "card:change:submit",
|
||||
Permissions: []string{"cpm:iot:tSimCardChange:batch:submit"},
|
||||
},
|
||||
{
|
||||
Feature: "card:allocate",
|
||||
Permissions: []string{
|
||||
"cpm:iot:tSimCardAllocate:targets&cpm:iot:tSimCardAllocate:input:prepare&cpm:iot:tSimCardAllocate:batch:submit",
|
||||
},
|
||||
Routes: []string{"/pages/business/views/allocate/index"},
|
||||
},
|
||||
{
|
||||
Feature: "card:stop",
|
||||
Permissions: []string{"cpm:iot:tSimCardStop:input:submit"},
|
||||
},
|
||||
{
|
||||
Feature: "card:start",
|
||||
Permissions: []string{"cpm:iot:tSimCardStart:input:submit"},
|
||||
},
|
||||
{
|
||||
Feature: "card:restart",
|
||||
Permissions: []string{"cpm:iot:tSimCardRestart:input:submit"},
|
||||
},
|
||||
{
|
||||
Feature: "card:dataStart",
|
||||
Permissions: []string{"cpm:iot:tSimCardDataStart:input:submit"},
|
||||
},
|
||||
{
|
||||
Feature: "card:dataStop",
|
||||
Permissions: []string{"cpm:iot:tSimCardDataStop:input:submit"},
|
||||
},
|
||||
{
|
||||
Feature: "card:diagnosis",
|
||||
Permissions: []string{
|
||||
"cpm:iot:tSimdiagnose:info",
|
||||
"cpm:iot:tSimdiagnose:flow",
|
||||
"cpm:iot:tSimCardInfo:querySimSession",
|
||||
"cpm:iot:tSimCardInfo:querySimStatus",
|
||||
},
|
||||
Routes: []string{"/pages/business/views/diagnosis/index"},
|
||||
},
|
||||
{
|
||||
Feature: "warn:view",
|
||||
Permissions: []string{
|
||||
"cpm:iot:simWarningConfig:list",
|
||||
"cpm:iot:simWarningConfig:view",
|
||||
"cpm:iot:simWarningConfig:save",
|
||||
"cpm:iot:tSimWarningConfig:input:submit",
|
||||
"cpm:iot:tSimWarningConfig:range:submit",
|
||||
"cpm:iot:tSimWarningConfig:excel:submit",
|
||||
},
|
||||
Routes: []string{"/pages/business/views/warning/index"},
|
||||
},
|
||||
{
|
||||
Feature: "order:list",
|
||||
Permissions: []string{
|
||||
"cpm:common:tBalanceDetail:list",
|
||||
"cpm:common:tFinanceApply:list",
|
||||
"cpm:gen:tFinanceApply:list",
|
||||
},
|
||||
Routes: []string{"/pages/business/views/order/index"},
|
||||
},
|
||||
{
|
||||
Feature: "order:detail",
|
||||
Permissions: []string{
|
||||
"cpm:common:tBalanceDetail:view",
|
||||
"cpm:common:tBalanceDetail:list",
|
||||
"cpm:common:tFinanceApply:view",
|
||||
"cpm:common:tFinanceApply:list",
|
||||
"cpm:gen:tFinanceApply:list",
|
||||
},
|
||||
},
|
||||
{
|
||||
Feature: "customer:list",
|
||||
Permissions: []string{"cpm:agent:dept:list", "cpm:agent:tAgentEnterprise:list"},
|
||||
Routes: []string{"/pages/business/views/customer/index"},
|
||||
},
|
||||
{
|
||||
Feature: "customer:detail",
|
||||
Permissions: []string{"cpm:agent:tAgentEnterprise:view", "cpm:agent:tAgentEnterprise:list"},
|
||||
Routes: []string{"/pages/business/views/customerDetail/index"},
|
||||
},
|
||||
{
|
||||
Feature: "message:view",
|
||||
Permissions: []string{consts.SysCheckPermissionCurrentUser, "cpm:system:notice:list"},
|
||||
Routes: []string{"/pages/message/index", "/pages/message/views/cardDetail/index", "/pages/message/views/systemDetail/index"},
|
||||
},
|
||||
{
|
||||
Feature: "workorder:list",
|
||||
Permissions: []string{"cpm:gen:tWoMain:list"},
|
||||
Routes: []string{"/pages/business/views/workOrder/index"},
|
||||
},
|
||||
{
|
||||
Feature: "workorder:create",
|
||||
Permissions: []string{"cpm:gen:tWoMain:edit"},
|
||||
Routes: []string{"/pages/business/views/workOrder/index", "/pages/business/views/workOrderCreate/index"},
|
||||
},
|
||||
{
|
||||
Feature: "workorder:detail",
|
||||
Permissions: []string{"cpm:gen:tWoMain:view", "cpm:gen:tWoMain:list"},
|
||||
Routes: []string{"/pages/business/views/workOrderDetail/index"},
|
||||
},
|
||||
}
|
||||
|
||||
func appEnsureFeatureAccess(ctx context.Context, feature string) error {
|
||||
feature = strings.TrimSpace(feature)
|
||||
if feature == "" {
|
||||
return nil
|
||||
}
|
||||
configured, err := appAccessConfigured(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !configured || contexts.IsSuperAdmin(ctx) {
|
||||
return nil
|
||||
}
|
||||
userId := contexts.GetUserId(ctx)
|
||||
if userId == 0 {
|
||||
return gerror.New("登录已过期")
|
||||
}
|
||||
requiredCodes := appFeaturePermissionCandidates(feature)
|
||||
if len(requiredCodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
accessCodes, _, err := mcache.GetUserAccessCodeList(ctx, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, requiredCode := range requiredCodes {
|
||||
if appAccessCodesMatchExpression(accessCodes, requiredCode) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return gerror.New("暂无操作权限")
|
||||
}
|
||||
|
||||
func appAccessCodesMatchExpression(accessCodes []string, expression string) bool {
|
||||
parts := appPermissionExpressionParts(expression)
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, part := range parts {
|
||||
matched := false
|
||||
for _, accessCode := range accessCodes {
|
||||
if appMatchAccessCode(accessCode, part) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func appMatchAccessCode(pattern string, code string) bool {
|
||||
pattern = appNormalizePermissionCode(pattern)
|
||||
code = appNormalizePermissionCode(code)
|
||||
if pattern == "" || code == "" {
|
||||
return false
|
||||
}
|
||||
if pattern == code || pattern == "*" || pattern == "cpm:*" {
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(pattern, "*") {
|
||||
return strings.HasPrefix(code, pattern[:len(pattern)-1])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func appFeaturePermissionCandidates(feature string) []string {
|
||||
feature = appNormalizeFeatureKey(feature)
|
||||
if feature == "" {
|
||||
return nil
|
||||
}
|
||||
if feature == "*" {
|
||||
return []string{"*"}
|
||||
}
|
||||
codes := make([]string, 0)
|
||||
for _, rule := range appFeatureAccessRules {
|
||||
if rule.Feature != feature {
|
||||
continue
|
||||
}
|
||||
for _, permission := range rule.Permissions {
|
||||
codes = appendUniqueString(codes, appNormalizePermissionExpression(permission))
|
||||
}
|
||||
}
|
||||
if len(codes) == 0 && strings.HasPrefix(feature, consts.SysCheckPermissionPrefix) {
|
||||
codes = appendUniqueString(codes, appNormalizePermissionCode(feature))
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
func appFeaturesByPermission(permission string) []string {
|
||||
permission = appNormalizePermissionCode(permission)
|
||||
if permission == "" {
|
||||
return nil
|
||||
}
|
||||
features := make([]string, 0)
|
||||
for _, rule := range appFeatureAccessRules {
|
||||
for _, rulePermission := range rule.Permissions {
|
||||
if strings.Contains(rulePermission, "&") {
|
||||
continue
|
||||
}
|
||||
if appMatchAccessCode(permission, rulePermission) || appMatchAccessCode(rulePermission, permission) {
|
||||
features = appendUniqueString(features, rule.Feature)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return features
|
||||
}
|
||||
|
||||
func appRoutesByFeature(feature string) []string {
|
||||
feature = appNormalizeFeatureKey(feature)
|
||||
routes := make([]string, 0)
|
||||
for _, rule := range appFeatureAccessRules {
|
||||
if rule.Feature != feature {
|
||||
continue
|
||||
}
|
||||
for _, route := range rule.Routes {
|
||||
routes = appendUniqueString(routes, route)
|
||||
}
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
func appKnownPermissionCodes() []string {
|
||||
codes := make([]string, 0)
|
||||
for _, rule := range appFeatureAccessRules {
|
||||
for _, permission := range rule.Permissions {
|
||||
for _, part := range appPermissionExpressionParts(permission) {
|
||||
codes = appendUniqueString(codes, appNormalizePermissionCode(part))
|
||||
}
|
||||
}
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
func appAppendFeaturesByPermissionSet(accessPermissions []string, appendFeature func(string)) {
|
||||
if len(accessPermissions) == 0 || appendFeature == nil {
|
||||
return
|
||||
}
|
||||
for _, rule := range appFeatureAccessRules {
|
||||
for _, permission := range rule.Permissions {
|
||||
if appAccessCodesMatchExpression(accessPermissions, permission) {
|
||||
appendFeature(rule.Feature)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appMenuPermCandidates() []string {
|
||||
values := make([]string, 0)
|
||||
for _, permission := range appKnownPermissionCodes() {
|
||||
if permission == "" || permission == consts.SysCheckPermissionCurrentUser {
|
||||
continue
|
||||
}
|
||||
values = appendUniqueString(values, permission)
|
||||
if strings.HasPrefix(permission, consts.SysCheckPermissionMenuPrefix) {
|
||||
values = appendUniqueString(values, strings.TrimPrefix(permission, consts.SysCheckPermissionMenuPrefix))
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func appNormalizeFeatureKey(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
if value == "*" || value == "cpm:*" {
|
||||
return "*"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func appNormalizePermissionCode(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
for strings.HasPrefix(value, consts.SysCheckPermissionMenuPrefix+consts.SysCheckPermissionMenuPrefix) {
|
||||
value = strings.TrimPrefix(value, consts.SysCheckPermissionMenuPrefix)
|
||||
}
|
||||
if value == "*" ||
|
||||
value == consts.SysCheckPermissionCurrentUser ||
|
||||
value == consts.SysCheckPermissionTerminalUser ||
|
||||
strings.HasPrefix(value, consts.SysCheckPermissionMenuPrefix) ||
|
||||
strings.HasPrefix(value, "cpa:") ||
|
||||
strings.HasPrefix(value, consts.SysCheckPermissionRolePrefix) ||
|
||||
strings.HasPrefix(value, consts.SysCheckPermissionDeptPrefix) ||
|
||||
strings.HasPrefix(value, consts.SysCheckPermissionPostPrefix) ||
|
||||
strings.HasPrefix(value, consts.SysCheckPermissionUserPrefix) {
|
||||
return value
|
||||
}
|
||||
return consts.SysCheckPermissionMenuPrefix + value
|
||||
}
|
||||
|
||||
func appNormalizePermissionExpression(value string) string {
|
||||
parts := appPermissionExpressionParts(value)
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(parts, "&")
|
||||
}
|
||||
|
||||
func appPermissionExpressionParts(value string) []string {
|
||||
rawParts := strings.Split(strings.TrimSpace(value), "&")
|
||||
parts := make([]string, 0, len(rawParts))
|
||||
for _, part := range rawParts {
|
||||
part = appNormalizePermissionCode(part)
|
||||
if part != "" {
|
||||
parts = appendUniqueString(parts, part)
|
||||
}
|
||||
}
|
||||
return parts
|
||||
}
|
||||
Reference in New Issue
Block a user