前后端配置
Oak 项目里有两类名字很接近、但含义完全不同的配置:
src/configuration/access.ts:前端如何访问后端服务;- 页面或命名空间里的
route.access:web 页面是否允许当前用户进入。
这两者不要混在一起理解。access.ts 解决的是“请求发到哪里”;route.access 解决的是“这个页面能不能看”。
src/configuration/access.ts
access.ts 是前端连接后端的统一入口。模板里通常长这样:
import accessConfiguration from './access.dev';
export default accessConfiguration;
也就是说,业务代码一般不直接 import access.dev.ts、access.prod.ts 或 access.staging.ts,而是统一从 @project/configuration/access 取当前环境的访问配置。
真正的配置类型是 AccessConfiguration,核心字段是:
import { AccessConfiguration } from '@oak-domain/types/Configuration';
const accessConfiguration: AccessConfiguration = {
http: {
hostname: 'localhost',
port: 3001,
ssl: false,
path: 'oak-api',
},
timeout: 5000,
clockDriftDuration: 10000,
};
export default accessConfiguration;
其中:
http.hostname是后端服务域名;http.port是后端端口,本地开发常用;http.ssl决定使用http还是https;http.path是反向代理路径,例如 nginx 映射到后端 API 的路径;timeout是前端请求超时时间;clockDriftDuration是允许的前后端时钟漂移时间。
AccessConfiguration 还可以配置几个框架路由前缀:
const accessConfiguration: AccessConfiguration = {
routerPrefixes: {
aspect: '/aspect',
endpoint: '/endpoint',
bridge: '/bridge',
getSubscribePoint: '/socketPoint',
},
socketPath: '/socket',
http: {
hostname: 'localhost',
port: 3001,
},
};
这些前缀一般不需要改。只有当后端路由或网关规则被项目刻意调整时,才应该同步配置。
它在哪里被使用
access.ts 最直接的消费者是 src/config/connector.ts:
import SimpleConnector from '@oak-domain/utils/SimpleConnector';
import accessConfiguration from '@project/configuration/access';
import { makeException } from '@project/types/Exception';
import { EntityDict } from '@project/oak-app-domain';
import FrontendRuntimeContext from '@project/context/FrontendRuntimeContext';
const connector = new SimpleConnector<EntityDict, FrontendRuntimeContext>(
accessConfiguration,
makeException
);
export default connector;
SimpleConnector 会根据 accessConfiguration 拼出:
- aspect 调用地址;
- endpoint 调用地址;
- bridge 地址;
- socket 订阅地址。
随后,生成的 src/initialize.server.ts 会把这个 connector 传给 @oak-frontend-base/initialize,web、小程序、native 的平台入口再统一 import @project/initialize。因此,access.ts 配错时,现象通常不是某个页面单独失败,而是前端所有 aspect、endpoint、订阅或缓存同步请求都可能连不上后端。
加密连接 EncConnector
如果项目需要在 Oak connector 层对请求和响应做加密,可以用 oak-internal-sdk 提供的 EncConnector 替换默认的 SimpleConnector。
EncConnector 的入口通常这样引入:
import { EncConnector } from '@oak-internal-sdk/utils/EncConnector';
这个入口会按编译平台选择实现:web、小程序、native 使用前端实现;server 使用后端实现。因此同一个 import 在前后端可以写一样,但构造参数不能完全一样。
前端侧的 src/config/connector/index.web.ts 可以这样写:
import accessConfiguration from '@project/configuration/access';
import { makeException } from '@project/types/Exception';
import { EntityDict } from '@project/oak-app-domain';
import FrontendRuntimeContext from '@project/context/FrontendRuntimeContext';
import { EncConnector } from '@oak-internal-sdk/utils/EncConnector';
const connector = new EncConnector<EntityDict, FrontendRuntimeContext>({
configuration: accessConfiguration,
makeException,
allowUnsafe: process.env.NODE_ENV !== 'production',
});
export default connector;
前端不需要 sessionStore。如果 allowUnsafe 为 false,前端会先通过内置 aspect oak:connector:key-exchange 和后端做密钥交换,然后用会话密钥加密后续请求中的 context 和 data。开发环境可以临时把 allowUnsafe 设为 true,让前端跳过密钥交换、继续发送未加密请求;生产环境一般应关闭。
后端侧也必须使用 EncConnector,否则后端无法解析前端发来的加密请求。后端的 src/config/connector/index.backend.ts 通常需要提供会话存储:
import accessConfiguration from '@project/configuration/access';
import { makeException } from '@project/types/Exception';
import { EntityDict } from '@project/oak-app-domain';
import FrontendRuntimeContext from '@project/context/FrontendRuntimeContext';
import { EncConnector } from '@oak-internal-sdk/utils/EncConnector';
import { createRedisSessionStore } from '@oak-internal-sdk/adaptor/redisAdaptor';
import redisConfig from '../../../configuration/redis.json';
const connector = new EncConnector<EntityDict, FrontendRuntimeContext>({
configuration: accessConfiguration,
makeException,
allowUnsafe: (options) => {
if (process.env.NODE_ENV !== 'production') {
return true;
}
const { headers } = options;
const oakVersion = headers['oak-version'];
const oakPlatform = headers['oak-platform'];
return !oakVersion && oakPlatform === 'mp';
},
sessionStore: createRedisSessionStore(redisConfig, {
keyPrefix: 'my_app:session:',
ttl: 24 * 60 * 60,
}),
cleanupInterval: 60 * 1000,
sessionMaxAge: 24 * 60 * 60 * 1000,
nonceMaxAge: 60 * 60 * 1000,
});
export default connector;
服务端 EncConnector 必须提供 sessionStore。oak-internal-sdk 提供了两种常用适配器:
createRedisSessionStore(...):适合生产部署,多个服务实例可以共享会话;createMemorySessionStore(...):适合单进程、本地测试或临时验证。
这两个创建函数只会在 process.env.OAK_PLATFORM === 'server' 时返回实例。自定义后端启动脚本如果没有先设置 OAK_PLATFORM=server,sessionStore 会是 undefined,服务端构造 EncConnector 时会抛出 error::backend.sessionStoreRequired。
一个项目如果要按平台拆 connector,可以采用类似结构:
src/config/connector/
index.ts // 默认导出后端实现,供 server 编译使用
index.backend.ts // EncConnector + sessionStore
index.web.ts // EncConnector,不传 sessionStore
index.mp.ts // 视项目情况使用 EncConnector 或 SimpleConnector
index.native.ts // 视项目情况使用 EncConnector 或 SimpleConnector
EncConnector 会额外使用这些 Oak 请求/响应头:
oak-session-idoak-encryptedoak-timestampoak-nonceoak-sequenceoak-versionoak-platform
Oak CLI 的 server 中间件会把 connector 的 getCorsHeader() 合并进允许请求头;开发和预发环境还会设置 connector 的响应头暴露。生产环境如果启用了自定义 CORS、nginx、网关或 CDN,需要额外确认这些头没有被拦截,并且响应头里的 oak-encrypted、oak-nonce 等能被浏览器读取。否则前端可能无法解密响应,或者 SSE 加密流无法正确解析。
EncConnector 也支持 endpoint 和 SSE endpoint。后端 endpoint 如果配置了 useConnector,Oak CLI 会先调用 connector.parseRequest(...) 解密请求;SSE endpoint 还会通过 serializeSSEEndpointResult(...) 加密 data: 数据包。加密 SSE 要求前后端 connector 同步升级,不能只升级其中一端。
迁移时最容易出错的地方有三个:
- 只把前端改成
EncConnector,后端仍然是SimpleConnector; - 后端没有提供
sessionStore,或者启动时没有设置OAK_PLATFORM=server; - 生产代理没有放行或暴露 Oak 加密相关 header。
多环境写法
模板通常会放三份访问配置:
src/configuration/access.dev.tssrc/configuration/access.staging.tssrc/configuration/access.prod.ts
本地开发常见写法是:
import { AccessConfiguration } from '@oak-domain/types/Configuration';
import { port } from './common';
export const hostname = 'localhost';
const accessConfiguration: AccessConfiguration = {
http: {
hostname,
port,
},
};
export default accessConfiguration;
生产环境如果通过 nginx 代理,常见写法是:
import { AccessConfiguration } from '@oak-domain/types/Configuration';
import { nginxServerProxyPath } from './common';
export const hostname = 'www.your-site.com';
export const ssl = true;
const accessConfiguration: AccessConfiguration = {
http: {
hostname,
ssl,
path: nginxServerProxyPath,
},
};
export default accessConfiguration;
access.ts 是最终统一出口。当前模板不会因为文件名存在就自动替你选择 access.prod.ts;如果项目有预发、生产构建,需要确保 access.ts 或项目自己的构建流程导出正确的环境配置。
src/configuration/server.ts
server.ts 是后端服务自己的运行配置,类型是 ServerConfiguration。它和 access.ts 经常引用同一批常量,但职责不同:
access.ts给前端 connector 用,描述“前端访问后端的地址”;server.ts给后端启动、代理和部署用,描述“后端自己怎么监听、如何暴露给 nginx”。
模板里的 server.ts 会从 access.dev.ts、access.staging.ts、access.prod.ts 读取域名和 ssl 配置,再根据 process.env.NODE_ENV 选择:
const serverConfiguration: ServerConfiguration = {
workDir: {
path: join(__dirname, '..', '..'),
},
port,
hostname: HostnameDict[process.env.NODE_ENV as string],
nginx: NginxConfDict[process.env.NODE_ENV as string],
};
所以部署时要同时检查两边:
- 前端构建产物里
access.ts指向的后端地址是否正确; - 后端启动时
NODE_ENV、server.ts、nginx 路径和实际监听端口是否一致。
src/configuration/index.ts
src/configuration/index.ts 是另一类配置入口,它导出的是 CommonConfiguration,会参与 Oak 前端运行时和内置 checker 初始化。模板中通常包含:
import attrUpdateMatrix from './attrUpdateMatrix';
import { CommonConfiguration } from '@oak-domain/types/Configuration';
import { actionDefDict } from '@project/oak-app-domain/ActionDefDict';
import { selectFreeEntities, authDeduceRelationMap, updateFreeDict } from './relation';
import cacheSavedEntities from './cache';
import { EntityDict } from '@project/oak-app-domain';
export default {
attrUpdateMatrix,
actionDefDict,
authDeduceRelationMap,
selectFreeEntities,
updateFreeDict,
cacheSavedEntities,
} as CommonConfiguration<EntityDict>;
这一组配置不是网络地址,而是 Oak 运行时规则,例如:
attrUpdateMatrix:属性更新矩阵,生成内置 checker;actionDefDict:实体 action / 状态矩阵定义;authDeduceRelationMap:权限关系推导;selectFreeEntities:允许自由查询的对象;updateFreeDict:允许自由更新的对象和动作;cacheSavedEntities:前端缓存持久化对象。
如果你是在改“前端访问哪个后端”,看 access.ts;如果你是在改“哪些对象能查、哪些属性能改、哪些 action 合法”,看 configuration/index.ts 以及它引用的 relation.ts、attrUpdateMatrix.ts 等文件。
页面访问控制 route.access
页面访问控制写在页面或命名空间的 index.config.ts,和 src/configuration/access.ts 没有直接关系。例如:
import { CreatePageConfig } from '@oak-frontend-base/config';
export default CreatePageConfig({
route: {
access: { type: 'login' },
},
});
常见取值包括:
public:直接允许;login:需要登录;root:需要 root;deny:始终拒绝;relation:按 Oak 关系权限判断;operation:用前端 checker 判断某个 Oak operation 是否允许;anyOf/allOf:组合多条规则。
组合规则使用 items:
export default CreatePageConfig({
route: {
access: {
type: 'allOf',
items: [
{ type: 'login' },
{ type: 'ref', refs: ['../system/detail'] },
],
},
},
});
也可以直接写规则数组,数组按 anyOf 语义处理。relation 规则使用 anyOf,operation 规则使用 target,不要把 anyOf / allOf 写成没有 items 的裸对象。
命名空间还可以通过 access.unconfiguredAccess 声明没有单独配置访问规则的页面默认如何处理:
export default CreateNamespaceConfig({
access: {
unconfiguredAccess: 'deny',
},
});
因此,排查访问问题时可以按这个顺序看:
- 前端请求是否打到了正确后端:看
src/configuration/access.ts和src/config/connector.ts; - 后端是否按正确端口和代理路径启动:看
src/configuration/server.ts; - 页面是否被路由权限拦住:看页面
index.config.ts的route.access和命名空间access.unconfiguredAccess; - operation 类访问控制是否被 checker 拦住:继续看
src/checkers、attrUpdateMatrix.ts和权限配置。