[在招] 核心业务说明
在招的核心业务提供一套开源、轻量的软件解决方案, 帮助平台型企业或组织:
- 管理平台内多企业的岗位招聘信息发布;
- 面试进展跟进(简历投递、结果反馈、数据汇总);
- 招聘讯息推荐及营销活动管理;
平台型企业大部分处于某个垂直细分领域, 也有可能运营管理多领域的招聘信息.
亦可以作为单个企业的招聘管理系统来部署使用.
概念拆解
领域 Field
对招聘平台运营方向的定义, 在大多数场景下与行业同含义, 例如: 新闻出版、人工智能、精密制造等等, 企业可以自行定制管理.
- 每个领域下会有多个 岗位类型(PositionType) 数据;
企业 Company
平台入驻的企业主体定义, 除了基本信息外, 还会涉及两个关键环节:
- 企业申请入驻认证流程(名称冲突和保护政策, 实名认证)
- 团队成员及权限管理;
岗位 Position
- 岗位需要关联某个具体的岗位类型, 这个初始数据由所在领域模块定制;
- 岗位基础信息管理, 涉及多维度的硬性要求和限制 🚫;
- 岗位的要求和职责会支持 AI 🤖 智能生成;
- 职位会涉及状态管理, 上线/下线;
简历 Resume
有新想法, 待规划
营销活动管理
待整理.
账号体系
在招平台会有两大类账号 求职者用户 User 与管理账号 Account , 未来会有两个不同的应用入口:
账号类型 | 应用入口 |
---|---|
User | zaizhao.ren |
Account | platform.zaizhao.ren |
但是两类账户会由同一个 Auth Service 提供用户验证服务及资源访问控制策略;
用户角色 User
用户可以访问前端应用, 但是无法登录管理后台, 与 Account 账号数据是独立的;
管理账号角色 Account
平台 Platform
系统初始化时自动生成唯一的平台角色账号, 提供重置账号的接口能力;
平台账号等同于超级管理员角色;
企业 Company
平台账号可以对企业账号申请入驻流程进行审核管理;
企业可自行进行发起注销企业流程;
账号 Employer
用户主动在平台注册账号即可以成为普通管理账号(员工👋);
可以申请进行企业账号认证, 审核通过后该账号可以升级为企业角色账号;
群组 Group
平台账号和企业账号均可以创建群组 Group, 邀请其他人加入为群组成员;
群组成员 Member 可以拥有不同的数据权限级别:
群组成员 Member | 数据权限级别 |
---|---|
Admin | 高级权限 |
Normal | 普通权限 |
Access Policies 资源访问策略
账号类型 | 角色 🎭 | 资源 | 访问控制 |
---|---|---|---|
Account | Platform | Company | Read,Edit |
- | Company | Company | Read,Edit |
- | Employer | Company | Read |
- | Employer | Company | Read |
- | Member(admin) | Company | Read |
- | Member(normal) | Company | Read |
User | User | Company | Read |
其它
暂无.
快速入门 Gel 数据库
One data model and a unified query-layer, NO JOINS, NO ORM,
Gel 既继承 SQL 数据库的优势: 类型安全、高性能、可靠稳定以及支持事务处理等 (基于 Postgres 开发), 又充分利用面向对象的设计思路, 定义类型对象(包含属性和指向其它类型对象的链接) 来表示数据 (Graph-Relational Database 的核心思想), 解决 SQL 在易用性和深度查询时的性能问题.
- Strict, strongly-typed Schema
- Built-in support for schema migrations & branch manage
- Powerful and clean query language: EdgeQL
- Auth & AI Solutions
- Full support for SQL, fully Open Source
- 单机或集群轻松部署, 高性能和网络连接健壮稳定
对整个应用开发流程的意义:
- 简化在数据整理、领域知识梳理以及数据序列化方面的工作, 将逻辑和数据关联都变成数据库模式和查询的一部分, 应用程序的代码可以更专注于业务逻辑和问题领域的具体细节.
- Access Policies 作为安全后端提供对象级安全, 将大量的查询过滤及访问控制都放在数据库层, 避免意外的数据泄露.
- 内置简单强大的用户验证解决方案, 与数据库访问策略控制可以完全结合起来.
- 客户端 SDK, HTTP Query 与 GraphQL 查询支持, 方便集成扩展.
- 向量存储及 AI 能力的加持, 数据库扩展系统.
命令行工具 gel
如何安装和使用: https://docs.geldata.com/learn/cli
- Instance: 管理数据库实例
# Action: create | list | logs
gel instance create new_instance --version 6.1
# Manage: status | start | stop | restart | destroy
gel instance status -I new_instance
# 在指定实例运行查询 Query
gel query "select 3.14" -I new_instance
gel instance --help # 更多功能
- Project: 让本地开发更简单.
Project 可以把本地的项目文件夹和数据库实例关联起来.
gel project init # 初始化项目: link instance
# Generate: gem.toml、dbschema(*.gel & migrations)
# 可以手动指定关联数据库实例, 支持远程实例
gel instance link
gel project unlink # 取消关联, -D 参数可以同时删除实例
gel project info # 查看项目信息
gel project upgrade --help # 升级实例版本
# 进入数据库查询 REPL
gel # 等同于 gel -I {instance_name}
- 数据库可视化管理:
gel ui
📄 数据模型 Schema, Migration & Branches
数据模型定义是业务系统的基石, Gel 提供强类型、高级的 SDL(Schema definition language)语言, 以在招的业务系统为例:
Login to Gel WebUI for dev server
基础概念
数据都用 类型对象(type Object) 来定义:
# 可以省略的关键字: property、optional、single、link
abstract property email_prop { # Abstract property
readonly := true; # 只读属性,不允许修改
}
type Player {
property required email: str { # str is a Scalar Type
extending email_prop;
};
# link 代表类型对象之间的关系, 是有方向的, 1-n 或 m-n
# Player(source) -> multi Game(target)
optional multi link games: Game {
# ensures a one-to-many relationship
constraint exclusive; # 约束条件: 唯一
on source delete allow; # deletion policy
};
}
type Game {
required name: str { # required 指必填属性
default := 'Unknown'; # 设置默认值
};
index on (.name) { # 设置索引
annotation description := 'Indexing all users by name.';
};
}
- Properties 属性 | Links 链接 | Primitives 原始类型
- Constraints 条件约束 | Indexes 索引
- Computed properties and links | Link properties
gel describe object [options] name 与 gel describe schema 命令可以辅助我们在命令行查看 schema 定义.
Abstract 抽象与 Inheritance 继承
# declare reusable, user-defined constraint types.
abstract constraint in_range(min: anyreal, max: anyreal) {
errmessage :=
'Value must be in range [{min}, {max}].';
using (min <= __subject__ and __subject__ < max);
}
abstract link link_with_strength {
strength: int64 {
constraint in_range(0, 100);
};
index on (__subject__@strength);
}
abstract type Person {
name: str;
multi friends: Person {
extending link_with_strength;
};
}
abstract annotation admin_note; # 自定义注释
type Student extending Person {
overloaded name: str { # overloaded property
constraint exclusive;
}
overloaded multi friends: Student;
annotation admin_note := 'system-critical';
}
Triggers 触发器 与 Mutation rewrites
用审计日志(Audit Log)的例子来学习使用触发器:
type Log {
action: str;
timestamp: datetime {
default := datetime_current();
}
target_name: str;
change: str;
}
type Person {
required name: str;
# also for insert or delete
trigger log_update after update for each
when (<json>__old__ {**} != <json>__new__ {**})
do (insert Log {
action := 'update',
target_name := __new__.name,
change := __old__.name ++ '->' ++ __new__.name
});
}
- Triggers may also be used for validation.
操作重写可以方便我们在数据插入和更新时进行拦截, 对数据进行修改;
type Post {
required title: str;
required body: str;
modified: datetime {
rewrite insert, update using (datetime_of_statement())
}
}
Access Policies 安全策略: Object-level Security
Access policies can greatly simplify your backend code, centralizing access control logic in a single place.
global current_user: uuid;
type User {
required email: str { constraint exclusive; }
}
type Post {
required title: str;
required author: User;
access policy author_has_full_access
allow all
using (global current_user ?= .author.id) {
errmessage := "User does not have full access";
}
}
# Policy types: all | select | insert | update | delete | update read | update write
工具能力: Aliases、Globals、Functions、Annotations
# Alias
alias digits := {0,1,2,3,4,5,6,7,8,9};
alias Account := User;
# Globals
required global now := datetime_of_transaction();
global current_user_id: uuid;
global current_user := (
select User filter .id = global current_user_id
);
# Functions
function add_user(name: str) -> User
using (
insert User {
name := name,
joined_at := std::datetime_current(),
}
);
# Annotations
type Status {
annotation title := 'Activity status';
annotation description := 'All possible user activities';
required name: str {
constraint exclusive
}
}
数据库扩展 Extensions
gel extension list
Migrations
可以记录数据库 Schema 的变更历史, 随时回滚, 并将模型 SDL 自动转换为数据库可以理解的 DDL(data definition language) 序列, 方便在开发过程中轻松的改进数据模型.
gel migration create # 生成迁移记录
gel migrate # 应用迁移到数据库
Branch
代码仓库分支的概念同理, 可以使用 gel branch
命令方便地创建、删除和切换分支, 简化开发协作流程.
Module
可以理解为一个命名空间, 复杂项目可以用 module 更好的划分 Schema 定义.
查询语言: EdgeQL
EdgeQL 是比 SQL 强大的下一代查询语言, 设计原则:
- Compatible with modern languages;
- Strongly typed;
- Designed for programmers;
- Easy deep querying, NO JOINS;
- Composable;
简单的查询示例:
# insert, select, filtering, ordering, pagination
with new_movie := (insert Movie { title := 'Ne Zha', release_year := 2021 })
select Movie {
id, title, release_year, actors,
title_upper := str_upper(.title),
cast_size := count(.actors)
}
filter .release_year > 2020 and .title ilike "ne zha%"
order by .title
offset 0 limit 10;
# update or delete action
update Movie
filter .title = "Doctor Strange 2"
set {
actors += (select Person filter .name = "Rachel McAdams")
};
# group 语句
group Movie { title, actors: { name }}
by .release_year;
Everything is a set
All values in EdgeQL are actually sets 集合: a collection of values of a given type. All elements of a set must have the same type.
select distinct {'a', 'a', 'b', 'c'}; # 集合去重
select 'd' in {'a', 'b', 'c'}; # 是否存在于一个集合
select {1, 2, 3} union {3, 4}; # 合并集合
select {1, 2, 3} intersect {3, 4}; # 集合交集
select <str>{} ?? 'default'; # 集合为空时取默认值
事务处理 Atomic Transactions
https://docs.geldata.com/reference/edgeql/transactions
client.transaction(async tx => {
await tx.execute(`update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 }`);
await tx.execute(`update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 }`);
});
HTTP & Client Libraries
EdgeQL over HTTP
启用扩展, 记得创建并应用迁移:
# dbschema/default.gel
using extension edgeql_http;
# terminal
curl -G https://<cloud-instance-host>:<cloud-instance-port>/branch/main/edgeql \
-H "Authorization: Bearer <secret-key>" \
-H "X-EdgeDB-User: admin" \
--data-urlencode "query=select Person {*};"
# Support POST & GET Methods, fields:
- query
- variables
- globals
GraphQL'
启用扩展, 记得创建并应用迁移:
# dbschema/default.gel
using extension graphql;
# terminal
curl \
-H "Content-Type: application/json" \
-X POST http://localhost:10787/branch/main/graphql \
-d '{ "query": "query getMovie($title: String!) { Movie(filter: {title:{eq: $title}}) { id title }}", "variables": { "title": "The Batman" }, "globals": {"default::current_user": "04e52807-6835-4eaa-999b-952804ab40a5"}}'
Explore the GraphQL schema and write queries: https://github.com/graphql/graphiql
- Endpoint: http:/localhost:port/branch/main/graphql/explore
Client Libraries
不同语言的客户端 SDK 基本都会实现一个 Client 客户端类:
- Manages a pool of physical connections to your Gel instance.
- Resolving connections: GEL_DSN
- Executing queries, Under the hood, this query is executed using Gel's efficient binary protocol.
Client Libraries: https://docs.geldata.com/reference/clients
Connection parameters: https://docs.geldata.com/reference/reference/connection#ref-reference-connection
其它
- 快速学习: https://docs.geldata.com/learn/edgeql
- Reference: https://docs.geldata.com/reference/edgeql
- 查询分析: https://docs.geldata.com/reference/cli/gel_analyze#ref-cli-gel-analyze
内置的 Auth 解决方案 🔐
大多数应用都需某种形式的用户认证, 通常的办法是自己造轮子重复开发, 或选择 Auth0 或者 Clerk 这样的托管服务.
直接使用 Gel 内置、易于集成的 Auth 解决方案也是一个选择:
- 利用内置的身份验证 UI 可以快速搭建应用, 可随时替换为定制的 UI;
- 深度集成: 部署 Gel 意味着您同时部署了 Auth(启用扩展即可), 无需复杂的配置环节;
- access policies for role-based access control
- layer multiple authentication factors onto a single unified User type
- full control over how your authentication data is stored and managed.
- 多种用户验证方式支持(可自行扩展)
- 相对于托管服务按照活跃用户或者验证次数收费, 成本更低;
using extension auth;
Custom Authentication API:
- Login in: POST /authenticate
- body(json): challenge | email | password | provider | redirect_to | redirect_to_on_signup
- response: { code }
- Register user: POST /register
- body(json): challenge | email | password | provider | verify_url
- response: { code, identity_id }
- Get auth token: GET /token
- params: code | verifier
- response: { auth_token, id_token, provider_token }
- Verify Email Link: POST /verify
- body(json): verification_token | provider
- response: { code }
- Send Reset-Password Email: POST /send-reset-email
- body(json): email | provider | reset_url | challenge
- response: { email_sent }
- Reset Password: POST /reset-password
- body(json): reset_token | password | provider
- response: { code }
Magic Link Auth:
- Register user: POST /magic-link/register
- body(json): challenge | email | provider | callback_url | redirect_on_failure
- Sign In with email: POST /magic-link/email
- body(json): challenge | email | provider | callback_url
- Get auth token
WebAuthn:
- Get the options for registering: GET /webauthn/register/options
- params: email
- GET /webauthn/authenticate/options
- params: email
- POST /webauthn/register
- body(json): provider | email | credentials | verify_url | user_handle | challenge
- POST /webauthn/authenticate
- body(json): provider | email | assertion | challenge
- response: { code }
用户注册成功后与用户进行绑定(1-1 or 1-m):
type User {
required email: str;
name: str;
required identity: ext::auth::Identity {
constraint exclusive;
};
}
...
# After POST /register response
if ("identity_id" in registerJson) {
await client.query(`
with
identity := <ext::auth::Identity><uuid>$identity_id,
emailFactor := (
select ext::auth::EmailFactor filter .identity = identity
),
insert User {
email := emailFactor.email,
identity := identity
};
`, { identity_id: registerJson.identity_id });
}
如何增加身份验证方式? 比如验证码或者扫码登录.
相关阅读:
- OAuth 2 Explained: https://www.youtube.com/watch?v=ZV5yTm4pT8g
- How PKCE Works: https://cloudentity.com/developers/basics/oauth-extensions/authorization-code-with-pkce/
- System Design: https://bytebytego.com/
- NextJS Template: https://github.com/geldata/nextjs-gel-auth-template
- Rust Example: https://github.com/dustypomerleau/audit/blob/main/src/auth.rs
AI 集成方案
基于 OpenAI 构建一个 Ask Bot 的 RAG 应用.
- https://docs.geldata.com/learn/guides/ai/edgeql
- https://docs.geldata.com/learn/tutorials/ai_fastapi_searchbot
- https://docs.geldata.com/resources/guides/tutorials/chatgpt_bot
- https://www.geldata.com/blog/chit-chatting-with-edgedb-docs-via-chatgpt-and-pgvector
Embedding 向量嵌入
- pgvector: https://docs.geldata.com/reference/stdlib/pgvector
🛠️ 系统管理
🎭 角色管理
# 创建角色, not possible to create non-superuser roles for now.
create superuser role project;
# 无密码登录(通常用于本地数据库开发)
configure instance insert Auth {
comment := 'passwordless access',
priority := 1,
method := (insert Trust),
};
# 设置密码登录
alter role project
set password := 'super-password';
# 增加一个优先级更高的 Auth 方法
configure instance insert Auth {
comment := 'password is required',
priority := 0,
method := (insert SCRAM),
user := 'project'
};
# 移除 Auth Method 方法
configure instance reset Auth filter Auth.method is Trust;
gel -I {INSTANCE_NAME} --user project --password
- configure 命令的使用说明;
- Roles Reference
gel-server 及环境变量的数据
gel info
# a catchall for logs and various caches.
| Cache │ /root/.cache/edgedb/
# contains auto-generated credentials
│ Config │ /root/.config/edgedb/
│ Install │ /root/.local/bin/
# contains the contents of all local Gel instances.
│ Data │ /root/.local/share/edgedb/data/
# the home for running processes/daemons.
│ Service │ /root/.config/systemd/user/
环境变量
GEL_SERVER_USER
# If set to anything other than the default username admin, the username specified will be created. The user defined here will be the one assigned the password set in GEL_SERVER_PASSWORD or the hash set in GEL_SERVER_PASSWORD_HASH.
GEL_SERVER_SECURITY
# When set to insecure_dev_mode, sets GEL_SERVER_DEFAULT_AUTH_METHOD to Trust, and GEL_SERVER_TLS_CERT_MODE to generate_self_signed (unless an explicit TLS certificate is specified). Finally, if this option is set, the server will accept plaintext HTTP connections.
GEL_SERVER_TLS_CERT_FILE/GEL_SERVER_TLS_KEY_FILE
GEL_SERVER_TLS_CERT_MODE
# require_file | generate_self_signed
More info: https://docs.geldata.com/reference/reference/environment
安全和性能层面的最佳实践
- PostgreSQL in 100 Seconds
- Video: https://www.youtube.com/watch?v=SpfIwlAYaKk
- This PostgreSQL tutorial helps you quickly understand PostgreSQL.
- https://www.geeksforgeeks.org/postgresql-tutorial/
- https://www.crunchydata.com/developers/tutorials