CodingTour
ARTS #199 | 家长开放日

第一次参加幼儿园的家长开放日活动,老师带着做早操,活力满满~

Algorithm

本周选择的算法题是:Destroying Asteroids

impl Solution {
    pub fn asteroids_destroyed(mass: i32, mut asteroids: Vec<i32>) -> bool {
        asteroids.sort_unstable();

        let mut mass = mass as i64;
        for asteroid in asteroids {
            if asteroid as i64 > mass {
                return false;
            } else {
                mass += asteroid as i64;
            }
        }
        true
    }
}

Review

Clean Code vs. Performant Code

梳理下作者的结论:

  • 不干净的代码性能更好,哪怕是像 Swift 这种在编译器层面深度优化过的语言也是
  • clean code 更合适扩展,便于写出好维护、好协作的代码
  • 不要盲目遵守某种教条,这不是选择 clean or not clean 的理由
  • 在没有严格限制性能的场景,优先选择 clean code;系统开始工作后,关注它的性能表现,并在开始优化之前进行测量

作者的结论基于文中给出的测试用例,但还是有不严谨的地方,比如给 class 增加 final 修饰符后可以做到全都要,添加 final 后其函数在运行期调用时避免了 vtable 式的查表,又保留了 clean 原则。

其次,编程语言、编译器在不断发展的过程中越来越注重零成本抽象,以 Rust 为例,我们让 ChatGPT 解释一下:

因此,我同意 “从 clean code 开始,其次是关注它的性能表现” 的观点,通常我会很在意代码是不是干净整洁的,如果有例外,应该清楚地记录为什么使用晦涩的、不易阅读的代码,我们努力构建的东西要有能提供长期支撑的底座。

Tip

用远程覆盖掉本地分支的内容:

git reset --hard origin/master

Share

为 dify 添加 ldap 支持

dify 是一个开源的 LLMOps 平台,可以快速创建 AI-Native 应用。但目前 dify 还是一个半成品的状态,邮箱邀请是一个假功能:

如何增加成员? #56

支持的登录方式也很有限:

what’s the plan to support new sso methods #16

看 dify 团队的描述,一时半会也等不到,好在 dify 代码清晰、结构一目了然,不用花太多功夫就能在本地添加对 ldap 的支持,接下来我们描述一下具体方向,涉及到:

  • 后端
  • 前端
  • Docker

首先是后端部分。

后端

由于 dify 的后端技术栈是 python + flask,我们先添加对应的 python 包:

python-ldap==3.4.3

接着在 api/services 目录下增加一个新的 ldap_service.py:

#!/usr/bin/python3
# -*-coding:utf-8-*-

__author__ = "your_name"

import ldap

LDAP_SERVER = 'ldap://host'
BASE_DN = 'base dn to search in'
OBJECT_TO_SEARCH = 'cn=%s'
ATTRIBUTES_TO_SEARCH = ['cn', 'mail']

def ldap_login(username, password):
    """
    用于进行 ldap 登录验证;如果验证失败,则返回 None。
    """
    try:
        connect = ldap.initialize(LDAP_SERVER)
        connect.set_option(ldap.OPT_REFERRALS, 0)  # to search the object and all its descendants
        connect.simple_bind_s(f"{OBJECT_TO_SEARCH % username},{BASE_DN}", password)
        result = connect.search_s(BASE_DN, ldap.SCOPE_SUBTREE, OBJECT_TO_SEARCH % username, ATTRIBUTES_TO_SEARCH)
    except ldap.INVALID_CREDENTIALS:
        return None

    object = result[0][1]
    name = object["cn"][0].decode()
    email = object["mail"][0].decode()
    return {
        "email": email,
        "name": name,
    }

接着修改 LoginApi.py 文件,调整 post 实现为:

@setup_required
    def post(self):
        """Authenticate user and login."""
        parser = reqparse.RequestParser()
        # parser.add_argument('email', type=email, required=True, location='json')
        parser.add_argument('email', required=True, location='json')
        parser.add_argument('password', type=valid_password, required=True, location='json')
        parser.add_argument('remember_me', type=bool, required=False, default=False, location='json')
        args = parser.parse_args()

        # todo: Verify the recaptcha

        try:
            account = AccountService.authenticate(args['email'], args['password'])
        except services.errors.account.AccountLoginError:
            return {'code': 'unauthorized', 'message': 'Invalid name or password'}, 401

        try:
            TenantService.switch_tenant(account)
        except Exception:
            raise AccountNotLinkTenantError("Account not link tenant")

        flask_login.login_user(account, remember=args['remember_me'])
        AccountService.update_last_login(account, request)

        # todo: return the user info

        return {'result': 'success'}

这个方法要做的事情其实很简单,就是干掉 email 校验。

然后修改 account_service.py 文件,引入 ldap_service:

from .ldap_service import ldap_login

并调整 authenticate 实现为:

@staticmethod
    def authenticate(name: str, password: str) -> Account:
        """authenticate account with name and password"""
        ldap_object = ldap_login(name, password)
        if ldap_object is None:
            raise AccountLoginError('Invalid name or password.')

        account = Account.query.filter_by(name=name).first()
        if not account:
            email = ldap_object["email"]
            name = ldap_object["name"]

            account = RegisterService.register(email, name, password)
            if not account:
                raise AccountLoginError('Invalid name or password.')

        if account.status == AccountStatus.BANNED.value or account.status == AccountStatus.CLOSED.value:
            raise AccountLoginError('Account is banned or closed.')

        if account.status == AccountStatus.PENDING.value:
            account.status = AccountStatus.ACTIVE.value
            account.initialized_at = datetime.utcnow()
            db.session.commit()

        # if account.password is None or not compare_password(password, account.password, account.password_salt):
            # raise AccountLoginError('Invalid name or password.')
        return account

不再和数据库中的 account 进行对比,取而代之的是总是通过 ldap 发起网络验证。

后端部分就完成了。

前端

前端部分更容易,只需要修改 web/app/signin 登录页面的表单:

  • 注释掉 handleEmailPasswordLogin 里的 email 校验
  • 将表单中的 {t(‘login.email’)} 修改为 {t(‘login.name’)}

其他地方不用动。

Docker

因为修改了源码,需要重新构建镜像文件。首先是修改用于构建 api 镜像的 Dockerfile,将:

...
RUN pip install -r requirements.txt
...

修改为:

RUN apt-get install -y libsasl2-dev python-dev libldap2-dev libssl-dev 
RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple

一方面是安装 python-ldap 所需的依赖,另一方面是设置镜像站点以加速安装过程。

The python-ldap is based on OpenLDAP, so you need to have the development files (headers) in order to compile the Python module. If you’re on Ubuntu, the package is called libldap2-dev.

https://stackoverflow.com/questions/4768446/i-cant-install-python-ldap

web 镜像也同理,将:

RUN npm install

修改为:

RUN npm install --sentrycli_cdnurl=https://cdn.npmmirror.com/binaries/sentry-cli --loglevel verbose

整个过程就算是完成了。

最后用 docker build -t tag_name . 构建完镜像后,把 tag_name 更新到项目根目录下的 docker/docker-compose.yaml 中即可。

Have Fun~!