Flask Web开发--2.多租户saas用户权限管理

本文详细介绍了多租户SAAS系统中的权限管理设计,包括数据模型、用户与角色关系及前端页面实现,确保不同组织间的数据隔离与权限控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

更多创客作品,请关注笔者网站园丁鸟,搜集全球极具创意,且有价值的创客作品


前言

对于多租户的SAAS系统,所有的操作都是以组织为单位的,所以相对于传统的单用户系统的用户权限管理,增加了一层组织的维度,一个注册企业下,又可以有完整的用户权限管理系统。

数据模型设计

如下是用权限系统的关系图:
在这里插入图片描述
组织在SAAS系统中的一切资源的最高阶组织形式,所以其他的对象都应该有一个组织的属性,对于用户也是如从,应该属于某个组织,组织与用户的关系应该是一对多的关系,如下是组织的Model对象。

class Organization(db.Model):
    """
    Create a Organization table
    """
    __tablename__ = 'organizations'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    key=db.Column(db.String(64), unique=True)
    country = db.Column(db.String(64))
    state = db.Column(db.String(64))
    city = db.Column(db.String(64))
    address = db.Column(db.String(64))
    status = db.Column(db.Integer)#0:disable,1:enable,2:temp for first register
    description = db.Column(db.String(200))
    users = db.relationship('User', backref='Organization',
                                lazy='dynamic',cascade='all, delete-orphan', passive_deletes = True)

用户用户可以登录到系统进行相关功能的操作,每个用户都属于某个组织,可以根据权限操作此组织下的资源,数据。每个用户都有一组角色信息,根据角色来判断其权限,如下是用户的model,由于后续将使用Flask-login进行用户登录注册的管理,所以User类继承自UserMixin,在其中扩展了Organization_id与组织相关联,其他的属性可以根据需求进行扩展。

class User(UserMixin, db.Model):
    """
    Create an User table
    """

    # Ensures table will be named in plural and not in singular
    # as is the name of the model
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(60), index=True, unique=True)
    username = db.Column(db.String(60), index=True)
    first_name = db.Column(db.String(60), index=True)
    last_name = db.Column(db.String(60), index=True)
    mobilephone = db.Column(db.String(20),index=True)
    password_hash = db.Column(db.String(128))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    organization_id = db.Column(db.Integer, db.ForeignKey('organizations.id'))
    status = db.Column(db.Integer)#0:disable,1:enable
    avatar = db.Column(db.String(60))# avatar pic name

    roles_user = db.relationship('Role_User', backref='User',
                                 lazy='dynamic',cascade='all, delete-orphan', passive_deletes = True)

    @property
    def password(self):
        """
        Prevent pasword from being accessed
        """
        raise AttributeError(_('password is not a readable attribute.'))

    @password.setter
    def password(self, password):
        """
        Set password to a hashed password
        """
        self.password_hash = generate_password_hash(password)

    def check_password_hash(self, password):
        return check_password_hash(self.password_hash,password)

    def verify_password(self, password):
        """
        Check if hashed password matches actual password
        """
        return check_password_hash(self.password_hash, password)

    def has_permission(self,permission):
        """
        Check if hashed the permission
        """
        for ru in self.roles_user:
            role=Role.query.filter(Role.id==ru.role_id).first()
            if role.name==permission:
                return True
        return False

has_permission函数根据权限名称来检索此用户是否有对应的角色权限

角色:每个角色表示一组操作的权限,可以操作系统相应的资源数据,用户与角色是多对多的关系,如下是角色的model,与用户类对象通过Role_User表进行关联。

class Role(db.Model):
    """
    Create a Role table
    """

    __tablename__ = 'roles'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60))
    status = db.Column(db.Integer)#0:disable,1:enable
    description = db.Column(db.String(100))
    caption = db.Column(db.String(60))

    users = db.relationship('Role_User', backref='Role',
                                 lazy='dynamic',cascade='all, delete-orphan', passive_deletes = True)

    def __repr__(self):
        return '<Role: {}>'.format(self.name)


class Role_User(db.Model):
    """
    Create a Role_User table
    """

    __tablename__ = 'role_users'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return '<Role_User: {}>'.format(self.name)

在本系统中设立如下三种角色:
在这里插入图片描述

  • Admin,企业的系统管理员,可以进行所有的操作,而且可以新建新的用户
  • Generic user,普通用户,只能进行功能性的操作,如SPC分析,田口实验设计
  • Resource Admin,资源管理,可以对企业资源进行管理,如设备,生产线,实验室等

页面表单form设计

对用户的操作,我们主要有如下三个操作,每个操作都对应相应的form。

  • 用户登录
    用户通过email,和密码来登录,同时有remember me的属性,所以只需要此三给元素即可,而且都为必填字段。同时有validate_email函数对输入email进行验证,判断是否已经注册,对于输入数据格式的验证本系统中都在前端网页验证。
    在这里插入图片描述
class LoginForm(FlaskForm):
    """
    Form for users to login
    """
    email = StringField('Email Address', validators=[DataRequired(), Email()])
    
    password = PasswordField('Password', validators=[DataRequired(message= _('the password can not be null.'))])
    remember_me = BooleanField('Remember me')
    submit = SubmitField('Sign In')

    def validate_email(self, field):
        if not User.query.filter_by(email=field.data).first():
            raise ValidationError(_('Invalid email.'))
  • 用户注册
    在这里我们只通过简单的邮件地址注册,并对输入的密码进行两次校验,所以验证函数有三个,分别对邮件是否已被注册,是否同意用户协议,两次输入密码是否一致进行校验。
    在这里插入图片描述
class RegistrationForm(FlaskForm):
    """
    Form for users to create new account
    """
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired(message= _('the password can not be null.'))])
    password_again = PasswordField('Password again', validators=[DataRequired(message= _('the password again can not be null.'))])

    agree_policy = BooleanField('Agree Policy')
    submit = SubmitField('Register')

    def validate_email(self, field):
        if User.query.filter_by(email=field.data).first():
            raise ValidationError(_('Email is already in use.'))

    def validate_agree_policy(self, field):
        if not field.data:
            raise ValidationError('you must agree the policy.')

    def validate_password_again(self, field):
        if field.data!=self._fields['password'].data:
            raise ValidationError('Inconsistent password twice')
  • 新建/编辑用户
    企业管理员可以新建用户,并分配角色,对已有用户进行编辑,角色修改,对应于UserForm,此form类中对用户名称,邮件地址,移动电话,角色都进行验证,保证相关数据的正确性。同时在这里增加了对企业license中用户数量进行验证
    在这里插入图片描述
class UserForm(FlaskForm):
    """
    Form for edit user
    """
    user_id = IntegerField('id')
    email = StringField('email', validators=[DataRequired()])
    username = StringField('username', validators=[DataRequired()])
    mobilephone = StringField('mobile phone')
    password = PasswordField('password')
    is_admin = StringField('is admin')
    is_resource_admin = StringField('is resource admin')
    is_generic_user = StringField('is generic user')
    submit = SubmitField('Submit')
    def validate_email(self, field):
        if self._fields['user_id'].data == -1:
            if User.query.filter(User.email==field.data).first():
                raise ValidationError(_('The user email is already in use.'))
        else:
            if User.query.filter(User.email==field.data,User.id!=self._fields['user_id'].data).first():
                raise ValidationError(_('The user email is already in use.'))
    def validate_mobilephone(self, field):
        if self._fields['user_id'].data == -1:
            if User.query.filter(User.mobilephone==field.data,User.organization_id==current_user.organization_id).first():
                raise ValidationError(_('The user mobilephone is already in use.'))
        else:
            if User.query.filter(User.mobilephone==field.data,User.organization_id==current_user.organization_id,User.id!=self._fields['user_id'].data).first():
                raise ValidationError(_('The user mobilephone is already in use.'))
    def validate_password(self, field):
        if self._fields['user_id'].data == -1:
            if field.data is None:
                raise ValidationError(_('the password can not be null.'))

    def validate_is_admin(self, field):
        if self._fields['user_id'].data == -1:
            if field.data=='is_admin':
                licenses =Organization_License.query.filter(Organization_License.organization_id==current_user.organization_id,Organization_License.license_id==License.id,License.name=='admin_numbers').first()
                quantity =Role_User.query.filter(Role_User.user_id==current_user.id,Role_User.role_id==Role.id,Role.name=='admin').count()
                if quantity>=licenses.quantity:
                    raise ValidationError(_('the numbers of admin you have created has more than the numbers of your license.'))

    def validate_is_resource_admin(self, field):
        if self._fields['user_id'].data == -1:
            if field.data=='is_resource_admin':
                licenses =Organization_License.query.filter(Organization_License.organization_id==current_user.organization_id,Organization_License.license_id==License.id,License.name=='resource_admin_numbers').first()
                quantity =Role_User.query.filter(Role_User.user_id==current_user.id,Role_User.role_id==Role.id,Role.name=='admin').count()
                if quantity>=licenses.quantity:
                    raise ValidationError(_('the numbers of resource admin you have created has more than the numbers of your license.'))

    def validate_is_generic_user(self, field):
        if self._fields['user_id'].data == -1:
            if field.data=='is_generic_user':
                licenses =Organization_License.query.filter(Organization_License.organization_id==current_user.organization_id,Organization_License.license_id==License.id,License.name=='generic_user_numbers').first()
                quantity =Role_User.query.filter(Role_User.user_id==current_user.id,Role_User.role_id==Role.id,Role.name=='admin').count()
                if quantity>=licenses.quantity:
                    raise ValidationError(_('the numbers of generic user you have created has more than the numbers of your license.'))

前端Web页面设计

前端页面都使用bootstrap进行设计,对应数据格式的常规验证都在前端进行,都是基本的h5页面,这里只贴代码
登录页面

<body class="authentication-bg">

    <div class="account-pages mt-5 mb-1">
        <div class="container">
            <div class="row justify-content-center">
                <div class="col-lg-5">
                    <div class="card mb-0">

                        <!-- Logo -->
                        <div class="card-body pt-4 pb-0 text-center">
                            <a href="index.html">
                                <span><img src="/static/assets/images/logo-qdo-dark.png" alt=""></span>
                            </a>
                        </div>

                        <div class="card-body p-4">
                            <form method="post" action="/login" name="login" class="needs-validation" novalidate>
                                {{ form.csrf_token }}
                                <div class="form-group">
                                    <input class="form-control" type="email" id="email" name="email" required placeholder="{{_('Enter your email')}}">
                                    <div class="invalid-feedback">
                                        {{_('Please provide a valid email.')}}
                                    </div>
                                </div>
                                <div class="form-group">
                                    <input class="form-control" type="password" required id="password" name="password" placeholder="{{_('Enter your password')}}">
                                    <div class="invalid-feedback">
                                        {{_('Please input the correct password.')}}
                                    </div>
                                </div>
                                <div class="form-group mb-3">
                                    <a href="pages-recoverpw.html" class="text-muted float-right"><small>{{_('Forgot your password?')}}</small></a>
                                    <div class="custom-control custom-checkbox">
                                        <input type="checkbox" name="remember_me" class="custom-control-input" id="checkbox-signin" checked>
                                        <label class="custom-control-label" for="checkbox-signin">{{_('Remember me')}}</label>
                                    </div>
                                </div>
                                {% if form.errors %}
                                <ul class="errors" style="color:#FF0033">
                                    {% for field_name, field_errors in form.errors|dictsort if field_errors %}
                                    {% for error in field_errors %}
                                    {{ form[field_name].label }}: {{ error }}
                                    {% endfor %}
                                    {% endfor %}
                                </ul>
                                {% endif %}
                                <div class="row justify-content-center">
                                    <div class="col-8">
                                        <div class="form-group mb-0">
                                            <button class="btn btn-primary btn-block" type="submit" name="Sign in">{{_(' Log In ')}}</button>
                                        </div>
                                    </div>
                                </div>
                            </form>
                            <div class="row mt-3">
                                <div class="col-12 text-center">
                                    <p class="text-muted mb-0">{{_('Do not have an account?')}} <a href="{{ url_for('auth.register') }}" class="text-muted ml-1"><b>{{_('Sign Up')}}</b></a></p>
                                </div>
                            </div>
                        </div>

                    </div>


                </div> <!-- end col -->
            </div>
            <!-- end row -->
        </div>
        <!-- end container -->
    </div>
    <!-- end page -->

    <!-- Required js -->
    <script src="/static/assets/js/app.js"></script>

</body>

注册页面

<body class="authentication-bg">

    <div class="account-pages mt-5 mb-1">
        <div class="container">
            <div class="row justify-content-center">
                <div class="col-lg-5">
                    <div class="card mb-0">
                        <!-- Logo-->
                        <div class="card-body pt-4 pb-0 text-center">
                            <a href="index.html">
                                <span><img src="/static/assets/images/logo-qdo-dark.png" alt=""></span>
                            </a>
                        </div>

                        <div class="card-body p-4">

                            <form method="post" action="/register" class="needs-validation" novalidate>
                                {{ form.csrf_token }}
                                <div class="form-group">
                                    <input class="form-control" type="email" id="email" name="email" required placeholder="{{_('Enter your email')}}">
                                    <div class="invalid-feedback">
                                        {{_('Please provide a valid email.')}}
                                    </div>
                                </div>

                                <div class="form-group">
                                    <input class="form-control" type="password" required id="password" name="password" placeholder="{{_('Enter your password')}}">
                                    <div class="invalid-feedback">
                                        {{_('Please input the correct password.')}}
                                    </div>
                                </div>

                                <div class="form-group">
                                    <input class="form-control" type="password" required id="password_again" name="password_again" placeholder="{{_('Enter your password again')}}">
                                    <div class="invalid-feedback">
                                        {{_('Please input the correct password again.')}}
                                    </div>
                                </div>

                                <div class="form-group">
                                    <div class="custom-control custom-checkbox">
                                        <input type="checkbox" class="custom-control-input" name="agree_policy" checked id="agree_policy">
                                        <label class="custom-control-label" for="agree_policy">{{_('I accept')}} <a href="#" class="text-muted">{{_('Terms &amp; Conditions')}}</a></label>
                                        <div class="invalid-feedback">
                                            {{_('Please acceept the agree policy')}}
                                        </div>
                                    </div>
                                </div>
                                {% if form.errors %}
                                <ul class="errors">
                                    {% for field_name, field_errors in form.errors|dictsort if field_errors %}
                                    {% for error in field_errors %}
                                    <span style="color:#FF0033">
                                        <li>{{ form[field_name].label }}: {{ error }}</li>
                                    </span>
                                    {% endfor %}
                                    {% endfor %}
                                </ul>
                                {% endif %}
                                <div class="row justify-content-center">
                                    <div class="col-8">
                                        <div class="form-group mb-0">
                                            <button class="btn btn-primary btn-block" type="submit"> {{_('Sign Up')}} </button>
                                        </div>
                                    </div>
                                </div>

                            </form>
                            <div class="row mt-3">
                                <div class="col-12 text-center">
                                    <p class="text-muted">{{_('Already have account?')}} <a href="{{ url_for('auth.login') }}" class="text-muted ml-1"><b>{{_('Log In')}}</b></a></p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <!-- end container -->
    </div>
    <!-- end page -->

    <!-- Required js -->
    <script src="/static/assets/js/app.js"></script>

</body>

新建/编辑用户

{% extends "layout.html" %}

{% block content %}

<!-- Start Content-->
    <div class="container-fluid">

        <!-- start page title -->
        <div class="row">
            <div class="col-12">
                <div class="page-title-box">
                    <div class="page-title-right">
                        <ol class="breadcrumb m-0">
                            <li class="breadcrumb-item"><a href="javascript: void(0);"><i class="feather icon-home"></i></a></li>
                            <li class="breadcrumb-item"><a href="javascript: void(0);">{{_('Auth')}}</a></li>
                            <li class="breadcrumb-item active">{{_('Users')}}</li>
                        </ol>
                    </div>
                    <h4 class="page-title">{{_('New User')}}</h4>
                </div>
            </div>
        </div>
        <!-- end page title -->
        <!-- form -->
        <div class="card">
            <div class="card-header">
                <h5>{{_('Info')}}</h5>
            </div>
            <div class="card-body">
                <form class="needs-validation" novalidate method="post" action="{{ url_for('auth.user_add')}}" name="user_add">
                    {{ form.csrf_token }}
                    <div class="form-row">
                        <div class="form-group col-md-4">
                            <input type="hidden" id="user_id" name="user_id" value="-1" />
                            <label for="username">{{_('Name')}}</label>
                            <input class="form-control" type="text" id="username" name="username" required placeholder={{_('Enter your name')}}>
                            <div class="invalid-feedback">
                                {{_('Please input a valid name.')}}
                            </div>
                        </div>
                        <div class="form-group col-md-8">
                            <label for="email">{{_('Email address')}}</label>
                            <input class="form-control" type="email" id="email" name="email" required placeholder={{_('Enter your email')}}>
                            <div class="invalid-feedback">
                                {{_('Please input a valid email.')}}
                            </div>
                        </div>
                    </div>
                    <div class="form-row">
                        <div class="form-group col-md-12">
                            <label for="mobilephone">{{_('Mobile Phone')}}</label>
                            <input class="form-control" type="text" id="mobilephone" name="mobilephone" required placeholder={{_('Enter your mobilephone')}}>
                            <div class="invalid-feedback">
                                {{_('Please input a valid mobile phone number.')}}
                            </div>
                        </div>

                    </div>
                    <div class="form-row">
                        <div class="form-group col-md-12">
                            <label for="password">{{_('Default Password')}}</label>
                            <input class="form-control" type="password" required id="password" name="password" placeholder={{_('Enter your password')}}>
                            <div class="invalid-feedback">
                                Please input the correct password.')}}
                            </div>
                        </div>
                    </div>
                    <hr>
                    <div class="form-group col-md-12">
                        <input type="checkbox" class="form-check-input" id="is_admin" name="is_admin" value="is_admin">
                        <span class="badge badge-danger">{{_('is Admin')}}</span>
                        <label class="form-check-label" for="c_user.has_permission('admin')">{{_('It has the whole permissions.')}}</label>
                    </div>
                    <div class="form-group col-md-12">
                        <input type="checkbox" class="form-check-input" id="is_resource_admin" name="is_resource_admin" value="is_resource_admin">
                        <span class="badge badge-info">{{_('is Resource Admin')}}</span>
                        <label class="form-check-label" for="is_resource_admin">{{_('It can manage the resource, like eqp, line, defect...')}}</label>
                    </div>
                    <div class="form-group col-md-12">
                        <input type="checkbox" class="form-check-input" id="is_generic_user" name="is_generic_user" value="is_generic_user">
                        <span class="badge badge-dark">{{_('is Generic User')}}</span>
                        <label class="form-check-label" for="is_generic_user">{{_('It can edit and view the quality data')}}</label>
                    </div>
                    {% if form.errors %}
                    <ul class="errors" style="color:#FF0033">
                        {% for field_name, field_errors in form.errors|dictsort if field_errors %}
                        {% for error in field_errors %}
                        {{ form[field_name].label }}: {{ error }}
                        {% endfor %}
                        {% endfor %}
                    </ul>
                    {% endif %}
                    {% if c_user.status!=0 %}
                    <button type="submit" class="btn  btn-primary float-right">{{_('Submit')}}</button>
                    {% else %}
                    <button type="submit" class="btn  btn-primary float-right" disabled>{{_('Submit')}}</button>
                    {% endif %}
                </form>
            </div>
        </div>
    </div> <!-- container -->
        {% endblock %}

功能逻辑

注册登录都使用Flask-login,只是在注册页面,增加了为新用户新建一个临时的组织信息。

@auth.route('/register', methods=['GET', 'POST'])
def register():
    """
    Handle requests to the /register route
    Add an user to the database through the registration form
    """
    form = RegistrationForm()

    if form.validate_on_submit():
        
        new_organization=Organization();
        new_organization.name=form.email.data
        new_organization.description=form.email.data
        new_organization.country="China"
        new_organization.state="ShangHai"
        new_organization.city="ShangHai"
        new_organization.address="ShangHai"
        new_organization.status=2#temp status
        new_organization.key=str(uuid.uuid4()).upper().replace('-','')
        db.session.add(new_organization)
        db.session.flush()

        user = User(email=form.email.data,username=form.email.data,password=form.password.data,organization_id=new_organization.id,status=1)
        db.session.add(user)
        db.session.flush()
        role = Role.query.filter(Role.name=='admin').first()
        user_role=Role_User(user_id=user.id,role_id=role.id)
        db.session.add(user_role)            

        db.session.commit()
        flash(_('You have successfully registered! You may now login.'))

        # redirect to the login page
        return redirect(url_for('auth.login'))

    # load registration template
    return render_template('register.html', form=form, title='Register')

新建用户
新建用户操作是由组织管理员完成,需要添加用户信息,同时为用户分配的角色,需要操作user,role_user两张表

@auth.route('/users/add', methods=['GET', 'POST'])
@login_required
def user_add():
    """
    Add a users to the database
    """

    form = UserForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,username=form.username.data,password=form.password.data,
                    mobilephone=form.mobilephone.data,
                    organization_id=current_user.organization_id)

        try:
            # add user to the database
            db.session.add(user)
            db.session.flush()

            if form.is_admin.data=='is_admin':
                admin_role = Role.query.filter(Role.name=='admin').first()
                user_role=Role_User(user_id=user.id,role_id=admin_role.id)
                db.session.add(user_role)
             
            if form.is_resource_admin.data=='is_resource_admin':
                resource_admin_role = Role.query.filter(Role.name=='resource_admin').first()
                user_role=Role_User(user_id=user.id,role_id=resource_admin_role.id)
                db.session.add(user_role)

            if form.is_generic_user.data=='is_generic_user':
                generic_user_role = Role.query.filter(Role.name=='generic_user').first()
                user_role=Role_User(user_id=user.id,role_id=generic_user_role.id)
                db.session.add(user_role)

            db.session.commit()
            flash(_('You have successfully added a new user.'))
        except Exception as e:
            # in case user name already exists
            db.session.rollback()
            flash(_('User name already exists.'))
            current_app.logger.exception(e)

        # redirect to the user page
        return redirect(url_for('auth.users_list'))

    # load user template
    return render_template('user_add.html',c_user=current_user,
                           form=form, title='Add user')
一、项目简介 构建一套基于云端的 USB 存储控制系统,通过 Web 控制台与部署在 Windows/macOS 客户端的 Agent 通信,实现对 USB 接口使用权限(只读、读写、禁用)的远程管理。系统支持租户隔离,具备商业 SaaS 属性、易用美观的 UI、全栈模块化设计。构建一套 多租户 USB 存储控制系统,用于企业远程控制并审计其所有 Mac/Windows 终端设备的 USB 存储行为,包括接口状态、文件读写操作等。系统支持多租户 SaaS 运营,提供现代化 Web 控制台、Agent 客户端及策略下发、审计溯源、安全机制等功能。 ⸻ 二、架构设计 • Web 服务端(Python 3 + FastAPI/Flask) • Agent 服务端(Python 3 + FastAPI,轻量 HTTP API) • Agent 客户端 • macOS:Swift 实现,隐藏后台启动、保护与卸载、安全通信 • Windows:C# 实现,功能对等 • 数据库:开发使用 SQLite,生产使用 MySQL,结构保持一致 • 全局配置:统一从 config.yaml 加载:域名/IP、端口、数据库连接、检查间隔、卸载密码 等 • 启动方式:命令行:python3 app.py {web|agent|both|setup} • CI/CD、监控、权限控制:基于 Token + RBAC,HTTPS+证书验证,日志与告警 ⸻ 三、数据库表结构 表名 字段 说明 tenants id、name、license_limit、status、created_at 租户信息 users id、tenant_id、username、password_hash、role、created_at 用户信息(租户或管理员) devices id、tenant_id、uuid、hostname、os、cpu、memory、battery、usb_status、last_seen、policy_id 设备基本信息与状态 policies id、tenant_id、name、mode(readonly/readwrite/disable)、group_id、created_at 策略定义 device_groups id、tenant_id、name、created_at 设备组 device_group_map id、device_id、group_id 组设备映射 config key、value 全局配置缓存 logs id、tenant_id、device_id、message、level、timestamp 日志与告警记录 ⸻ 四、API 路径设计 Web API • POST /api/login用户登录,返回 Token • GET /api/tenants、POST /api/tenants:查看/新增租户(仅管理员) • GET /api/devices、GET /api/devices/{id}:设备列表与详情 • GET/POST/PUT/DELETE /api/policies[/{id}]:策略 CRUD • GET/POST/PUT/DELETE /api/groups[/{id}]:分组 CRUD • GET/PUT /api/config:系统设置读取/写入 Agent Service API • POST /agent/check-in:设备上报基本资产信息 • GET /agent/license?uuid={uuid}:获取许可状态 • GET /agent/policy?uuid={uuid}:获取 USB 控制策略 • POST /agent/report:上报当前 USB 状态 ⸻ 五、模块与交互流程 Web 服务端 • 登录鉴权 + RBAC • 多租户查询与授权 • 策略、组与设备的 CRUD 与配置 • License 数量管理(自动计算租户已用设备数量) • 设备管理(统计、在线状态、详情) • 日志、告警展示与查询 • 设置页:Agent 通信间隔、卸载密码、网络接口等 Agent 服务端 • 提供轻量 REST 接口供客户端访问 • 检查租户许可、策略,返回 JSON(含策略版本号) Agent 客户端 • 自启动、隐藏进程、防卸载机制(卸载需输入密码) • 周期性(默认 5 分钟)向 Agent 服务端进行: 1. check-in(资产上报) 2. 获取 license:如无许可,停用 USB 策略 3. 获取 policy:如果有更新则本地缓存并执行 4. 上报 USB 当前状态 • 实时控制 USB 接口权限(依据 policy 本地生效,即使离线) ⸻ 六、UI 页面结构 1. 登录页 2. 首页 Dashboard:全局/租户统计图表、活跃设备比例、策略使用情况 3. 设备列表页:设备搜索、列表展示、设备状态过滤 4. 设备详情页:展示资产、组信息、策略、状态日志、手动同步按钮 5. 策略管理页:展示、创建、编辑、删除策略,可指定组或单个设备 6. 组管理页:分组 CRUD、组内设备管理 7. 系统设置页:通信设置、卸载密码设置、许可通知阈值 8. 日志与告警页:可按租户/设备/级别/时间查阅 9. Agent 下载页:根据平台提供 macOS/Windows 安装包,提示卸载密码设定 UI 样式建议: • 现代化仪表板风格 • 明亮主题,主色深蓝/蓝绿,按钮(新增、删除)动效精准,提示对话框确认 • 可用性高,清晰模块划分、响应式布局 ⸻ 七、商业价值 • 多租户支持 & License 管理化,适用于 B 企业对 C 企业分销 • SaaS 模式收费,Starter/Standard/Enterprise 套餐按设备数量分级定价 • 提供安全合规性,帮助用户满足数据审计和设备使用控制要求 • 可扩展更多 I/O 管制功能,如网络控制、安全隔离等 ⸻ 八、提示词:给 AI 生成代码/模块实现 A. 初始化项目与模块 # 初始化项目结构 – 创建目录:api、agent_server、agent_client、config – 初始化 FastAPI 应用,配置 config.yaml 加载模块 B. 数据模型定义(SQLAlchemy 示例) class Tenant(Base): __tablename__ = "tenants" id = Column(Integer, primary_key=True) name = Column(String, unique=True) ... C. Web API 实现(示例) @router.post("/api/policies") async def create_policy(...): # RBAC 判断 + MySQL/SQLite ORM 写入 + 返回 JSON D. Agent 服务端 @app.post("/agent/check-in") async def checkin(req: DeviceCheckin): ... E. Agent 客户端(Swift/C#)实现逻辑 Timer.scheduledTimer(withTimeInterval: config.check_interval) { checkIn(); getLicense(); getPolicy(); reportStatus() } F. UI 页面 • React/Vue 与 Ant Design 实现布局 • DeviceList、PolicyForm、GroupTree、DashboardCharts 组件 G. 安全、测试与部署 不使用Docker ,不使用nginx,通过 python3 run.py both 执行 本系统所有API和操作均严格遵循基于角色的访问控制(RBAC)、租户隔离、超级管理员、审计日志等最佳实践: - **RBAC权限模型**:分为超级管理员(super_admin)、管理员(admin)、租户用户(tenant_user)。不同角色可访问的API范围不同。 - **租户隔离**:非admin/super_admin用户仅能访问本租户数据,所有查询自动加租户过滤。 - **超级管理员**:可管理全局线索、Agent构建、全局配置等敏感操作。 - **审计日志**:所有关键操作均自动记录,便于合规与追溯。 - **权限错误**:所有权限不足返回均为中英双语,便于国际化。
最新发布
07-11
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DiegoRobot

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值