关于 sasjs-framework
sasjs-framework 是构建 SASjs 应用的静态参考技能,说明如何组织 Web 前端与 SAS 后端。内容涵盖项目布局、配置、服务契约和质量实践,适用于部署到 SAS 9、Viya 或 SASjs server 的应用。
主要功能
- 01标准应用布局
- 02按目标配置项目
- 03前端与 SAS 服务契约
- 04输入验证指引
- 05多目标代码组织
- 06测试与 lint 实践
适用场景
- 01规划 SASjs 应用结构
- 02配置面向多个目标的部署
- 03设计前端与 SAS 服务的数据交换
- 04检查输入安全和测试实践
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sasjs/ skills --skill sasjs-framework安装后在 Claude Code 中输入/sasjs-framework确认已加载。
附带文件sasjsconfig-schema.json
详细介绍
SASjs Framework - 构建 SASjs 应用
SASjs 应用 = Web 前端(可使用任意框架:Angular、React、原生)+ 按标准布局组织的 SAS 后端代码,由 @sasjs/cli 编译并部署到 SAS 9、Viya 或 SASjs 服务。前端通过 @sasjs/adapter 与 SAS 通信;后端服务通过 _webout 返回 JSON。
标准项目布局
sasjs/
sasjsconfig.json # project + target configuration
macros/ # project-specific macros (macroFolders)
services/ # web services called from the frontend
jobs/ # jobs (scheduled / flow / long-running)
programs/ # plain programs (initProgram, termProgram, utilities)
db/ # DDL + static data per library (sasjs db)
tests/ # tests run by `sasjs test`
mocks/ # mock responses for offline frontend dev (syncFolder)
doxy/ # extra doxygen content for `sasjs doc`
sasjsconfig.json
根配置文件保存默认值;targets[] 中的每个条目都可以覆盖这些默认值。主要部分:
macroFolders、binaryFolders- CLI 查找宏和二进制文件的位置serviceConfig.serviceFolders- 服务源文件夹;initProgram在每个服务运行前执行(用于设置库名、选项)jobConfig.jobFolders- 作业源文件夹programFolders- 随应用编译和部署的程序streamConfig-streamWeb: true会将构建后的前端流式传输到 SAS,使其由平台自身提供服务(无需单独的 Web 服务器);webSourcePath指向前端构建输出目录syncFolder- 同步到服务端的文件夹(例如 mocks)testConfig- 用于sasjs test的初始化/终止程序targets[]- 按环境覆盖配置:serverUrl、serverType(SAS9/SASVIYA/SASJS)、appLoc(部署根目录,例如/Public/app/myapp)、特定目标的macroFolders(例如用于平台适配层的targets/viya/macros_viya)、httpsAgentOptions、deployConfig
完整的 JSON Schema 随附于此文件旁的 sasjsconfig-schema.json 中;请依据该文件验证配置变更。通过 "$schema": "https://cli.sasjs.io/sasjsconfig-schema.json" 引用它。
Viya 上的流式前端文件(MIME 类型)
当 streamWeb: true 时,CLI 会使用 %mv_createfile 宏,将前端(index.html,按 streamServiceName 重命名,另包括 css/js)上传到 Viya Files 服务。该宏会以非常特定的方式创建文件,以确保文件能够正确流式传输:
- 向
/files/files发送 POST 请求,内容类型根据扩展名确定(%mf_mimetype) - 设置
typeDefName=file_html(通过%mv_getViyaFileExtParms),以便将文件识别为 HTML - 对 HTML/SVG 发送不含
attachment的Content-Disposition,使其能在浏览器中呈现
绝不要通过 filename filesrvc 文件引用和 data step 重写来原位更新流式前端文件——Files 服务随后会将其视为通用 blob,导致 MIME 类型丢失,应用也就无法继续流式传输(浏览器会下载文件或显示原始文本)。若要在运行时修改流式文件(例如修补 HTML 中的 compute contextname),请读取文件(filesrvc 文件引用可用于读取),将修改后的内容写入临时文件引用,然后使用 %mv_createfile(path=..., name=..., inref=...) 重新创建文件(该操作会删除旧文件,并以正确的 MIME 类型重新 POST 文件)。
服务契约(前端 <-> SAS)
- 适配器通过
services/<folder>/<name>发送 POST 请求,并附带输入表(对象数组)→ 工作数据集以 JS 键命名。 - 服务 SAS 代码在
initProgram之后运行;它读取输入、执行操作,并将输出 JSON 写入_webout。 - 使用 @sasjs/core 宏的常见模式:
/**
@file
@brief Example service returning data
<h4> SAS Macros </h4>
@li mp_jsonout.sas
@li mp_abort.sas
**/
/* validation / logic here */
%mp_jsonout(OPEN)
%mp_jsonout(OBJ,results,dslabel=results)
%mp_jsonout(CLOSE)
-
标头中的
<h4> SAS Macros </h4>@li条目不是文档,而是编译器的依赖项清单。服务调用的每个宏都必须列在其中,否则不会内联到构建中,部署的作业将在运行时失败,并报错Apparent invocation of macro X not resolved。如果某个已声明宏的@li依赖链恰好内联了未声明的调用(传递解析),未声明的调用可能会侥幸通过测试,但当该宏的依赖项发生变化时就会出错。编辑现有服务时,请重新核对正文中的%macro()调用与标头条目,并声明所有缺失项,包括mf_getuser.sas这类小型辅助宏。 -
出错时,使用
%mp_abort(...)(mf_abort已弃用)干净地中止,以便适配器在 JSON 中收到结构化错误,而不是不完整的响应。不要在%if/%else块内调用%mp_abort,因为宏处理器可能会在中止后继续执行。请改用iftrue=参数进行条件判断,例如:
%mp_abort(iftrue= (%mf_existds(work.results)=0)
,mac=&_program
,msg=%str(No results found)
)
如果中止发生在 %include 块内,SAS 无法干净地退出到 _webout;在 include 之后调用 %mp_abort(mode=INCLUDE)(不要放在任何宏封装内),它会检查 work.mp_abort_errds 中的中止状态。
清理服务输入(SAS 宏注入)
所有来自 Web 请求的值(输入表、URL 参数)都由攻击者控制。如果这些值在生成的代码中解析为宏变量——例如 libname 路径、set/from 语句、where 子句或 %include——那么空格、引号、分号以及 &/% 宏触发字符就会成为经典的 SAS 注入攻击媒介。
规则:
- 在任何
call symputx之前,先在数据步中使用 @sasjs/core 的%mp_validatecol(incol,RULE,outcol)验证输入。仅在验证通过时执行 symput,否则中止(%mp_abort(iftrue=...))。规则包括ISLIB(有效 libref)、ISNAME(有效 SAS 名称,例如成员/表名称)、LIBDS(LIBREF.DATASET)、FORMAT、ISINT、ISNUM。 - 如果存在白名单,请使用白名单:语法验证之后,在控制表中确认该值存在(例如通过主键查找),再用它构建路径或代码。
- 从控制表读取的值也可能被篡改,因此在将其传入生成代码的宏之前,同样要验证这些值。
- 对于必须回显到代码中的自由文本值(例如用户消息),请去除宏触发字符:使用
compress(value,'&%;')或对每个危险字符使用tranwrd。但最好不要将自由文本放入代码中。
%let libds=0;
data _null_;
set work.sascontroltable;
%mp_validatecol(libds,LIBDS,is_libds)
if is_libds=1 then call symputx('libds',libds);
else putlog 'ERR' 'OR: invalid libds: ' libds;
run;
%mp_abort(iftrue= ("&libds"="0")
,mac=&_program
,msg=%str(Invalid libds provided)
)
多目标规范
- 在共享文件夹中保持后端代码与平台无关;将特定于平台的垫片放入
targets/<name>/macros_*文件夹,并仅在相应目标上注册。 - @sasjs/core 中提供了平台能力宏(
mm_*元数据、mv_*Viya、ms_*server),不要手动按服务器类型进行分支处理。
质量门禁(遵循 Data Controller 等成熟应用的惯例)
- 修改任何
.sas文件后,都要运行sasjs lint;修复所修改文件中的所有警告。 - 代码检查器会强制要求所有位置使用 2 个空格缩进,包括
/* ... */块注释中的续行——注释文本绝不能使用 3 个或更多空格对齐。 - 后端逻辑有变更时,要添加测试并运行
sasjs test。测试宏时,始终使用%mp_assertscope(SNAPSHOT)/%mp_assertscope(COMPARE, ...)包裹被测宏,以捕获宏变量作用域泄漏;并使用%macro包裹任何包含平台分支的代码(测试程序中不能出现未封装的条件宏代码)。 - 在
sasjs/mocks中提供模拟数据,以便无需连接实时 SAS 服务器也能开发前端。 - 绝不自动提交或提升版本;发布由流水线驱动(遵循约定式提交)。
- Markdown 文件:不要硬换行——每个段落占一行。
- 应用必须支持离线和本地部署:前端包中不得包含外部 CDN 资源。
测试必须具备幂等性
测试文件必须能够重复运行并通过(包括在上次运行中途失败后)。
- 文件开头使用
%let syscc=0;——许多 DC 宏在&syscc>0时会在入口处中止,而且先前测试中的任何WARNING都会将syscc增至 4。 - 准备步骤要有防护:先删除再插入配置记录(处理上次中止运行遗留的数据),并重新创建物理表,而不要假设这些表不存在。
参考实现
查看现有应用,了解相关模式:文件夹布局、sasjsconfig.json 多目标配置、服务结构、流式构建,以及测试和模拟数据惯例,例如:
- https://git.datacontroller.io/dc/dc
- https://github.com/sasjs/react-seed-app
- https://github.com/sasjs/macro-dash
局限
此技能是用于构建 SASjs 应用的静态参考资料——提供有关项目结构、配置和服务契约的指导。它不会执行代码、运行 shell 命令、访问文件系统、连接数据库或发起网络请求。对文件路径、构建输出和部署目标的引用仅用于描述 SASjs 项目的结构——此技能本身不会创建、修改或访问这些路径。