Files
2026-09-02 10:23:24 +08:00

7.9 KiB
Raw Permalink Blame History

YhCore 开发规范

.NET 5.0 后端框架(参照 Java 版 bschclassmanage 迁移),仅框架无业务逻辑。

1. 解决方案结构

YhCore.sln
├─ src/
│  ├─ YhCore.WebApi       # 启动项:Controllers / Middleware / Filters / Attributes / Auth
│  ├─ YhCore.Service      # 业务服务层:Interfaces / Impl(不含业务时用 *Stub 占位)
│  ├─ YhCore.Repository   # 数据层:YHDbContext / IRepository<T> / Specification / DapperChannel
│  ├─ YhCore.Entity       # 数据库实体(旧 EF6 POCO 迁移而来,无导航属性)
│  ├─ YhCore.Model        # DTO / VO / Boa 平台占位模型
│  ├─ YhCore.Common       # 工具:ApiRoutes / UserContext / RedisUtil / PageUtil / ApiResult
│  └─ YhCore.Tasks        # 调度:QuartzHostedService / Jobs
└─ lib / docker / docs / sqlscript

依赖方向(单向,禁止反向引用):WebApi → Service → Repository → Entity;WebApi → Model ← Service;Common 不依赖任何上层。

底层框架(Yh.Platform.*):CAS 认证、用户上下文(BoaUserContext)、应用验签(AppAuthVerifier)、用户中心(OpenApi)均由底层框架提供:

  • 引用 DLL:Yh.Platform.Abstractions / Yh.Platform.AppAuth / Yh.Platform.Auth / Yh.Platform.OpenApi(lib 目录,源码在 E:\YHXT\net\YHBasePlatformDotnet)
  • Startup 用 services.AddYhPlatform(Configuration) 聚合注册(= AddYhAppAuth + AddYhAuth[含 AddYhOpenApi]),管道用 app.UseYhUserContext() 注入用户上下文
  • 旧自研 CAS/验签/用户中心实现已退役到 src/_legacy/(不参与编译,仅留档备份);业务代码取当前用户用 BoaUserContext.Get()

/oapi 开放渠道契约:应用间调用渠道,无需用户登录,凭签名票据直接访问:

  • 票据 = 请求头 X-App-Key / X-Timestamp / X-Signature(signature = Base64(HmacSHA256(METHOD + "\n" + path[?sortedQuery] + "\n" + timestamp, secret)),详见框架仓库 YHBasePlatformDotnet\BasePlatformDotnet\contract\sign-protocol.md)
  • 未带票据/票据非法 → 401 {code:4,msg:"缺少签名头…"/"签名校验失败…"};验签通过 → 放行到业务(无用户上下文,业务只可读 UserContextHolder.Get().AppKey 应用身份)
  • SSO 登录保护永不拦截 /oapi(SsoProtectionMiddleware 显式放行,即使 yh:sso:urlPattern 误配也不影响);服务端验签密钥解析链:配置内置应用(yh:app-auth AppKey/AppSecret)→ Redis sign:app:{appKey} → 本地缓存 → UserBase 兜底

/oapi 冒烟验证方法:本地联调/自测票据可用 scripts/oapi-sign.js 生成(对齐签名协议):

  • 生成票据:node scripts/oapi-sign.js GET "/oapi/hello?a=2&b=1" → 输出 appKey/ts/signature 三头
  • 直接出 curl:node scripts/oapi-sign.js --curl POST "/oapi/xxx" → 复制替换 PORT 即可
  • 密钥默认取开发联调密钥(yh:app-auth),生产环境用 APP_KEY / APP_SECRET 环境变量覆盖,禁止提交真实密钥到仓库
  • 断言基线:无票据 → 401 {code:4};合法票据 → 直达业务(404/业务结果);假票据 → 401;/api、/mobiapi 无 Cookie → 302 CAS(SSO 保护不受影响)

2. 强制规范

约定 说明
接口路径 一律用 [Route(ApiRoutes.ApiPath + "/xxx")],禁止硬编码 "api/...";渠道前缀见 ApiRoutes(api/mobiapi/oapi)
接口返回结构 与 Java 版保持一致(裸返回),全局 GlobalWrapResultFilter 自动统一包装(ApiResult{code,msg,data});需要裸返回用 [NoWrapResult] 显式排除;异常由 GlobalExceptionMiddleware 统一转失败响应
业务异常 Service 层抛 new DException(ErrorCodeUtil.From(code), "提示语")(框架 Yh.Platform.Abstractions);仅提示语可用便捷构造 new DException("提示语")(默认 Custom=5);中间件自动转统一失败响应
日志 只允许接口访问日志(ApiAccessLogMiddleware 自动记录)。业务代码禁止调用 ILogger 写日志;异常摘要由接口日志统一带出
缓存 Key 必须用 RedisConstants.KeyOf(...) 生成;过期时间用 RedisConstants.CACHE_EXPIRE_*
分页 页码从 0 开始(与 Spring Pageable 对齐);数据库分页用 IRepository<T>.PageAsync,内存分页用 PageUtil
动态条件 用 Specification.True<T>().And(...).AndIf(cond, ...) 拼装
操作日志 接口级审计日志,全局自动:SysOperationLogFilter(Startup 全局注册)每次 Controller 请求后落库 sys_operation_log(对齐 Java SysOperationLog,含 URI/参数/响应 GZIP/耗时/状态码/异常),无需任何标注;级别按 HTTP 方法推导(GET=QUERY/POST=ADD/PUT=EDIT/DELETE=DELETE);LogController 已显式排除防递归
实体主键 实体无 [Key] 时按约定推断(Id → SerID);新增表实体建议显式标注
数据库 默认 SQL Server 主库;DatabaseProvider=MySql 切换;缺表自动补建(Database:AutoCreateMissingTables,默认 true:启动时对比 EF 模型与数据库,缺失的表按模型自动 CREATE,幂等;已存在表的结构变更仍走 sqlscript 脚本)
数据库写操作 注意 RepositoryBase 的 Add/Update 需调用 SaveChangesAsync() 提交(UnitOfWork 模式)

3. 常用命令

dotnet build                                        # 编译
dotnet run --project src/YhCore.WebApi      # 本地运行(Swagger: /swagger/index.html)
dotnet watch run --project src/YhCore.WebApi

4. 环境配置(appsettings.json 或环境变量覆盖)

配置 默认值 说明
DatabaseProvider SqlServer SqlServer / MySql
Database:AutoCreateMissingTables true 启动时自动补建 EF 模型中有而数据库缺失的表(仅 SQL Server;幂等;已存在表不触碰,结构变更仍走 sqlscript)
ConnectionStrings__SqlServer localhost/EduCloudDocPrint 主库连接串(生产用环境变量,勿提交明文密码)
ConnectionStrings__Mysql localhost:3306 MySQL 备用连接串
Redis:ConnectionString localhost:6379,defaultDatabase=0 Redis 连接串
yh:auth:CookieName .yh.auth 认证 Cookie 名
yh:auth:CookieExpireMinutes 1 登录态固定过期(分钟),不滑动续期;票据存服务端内存(SessionTicketDataFormat),Cookie 只写 32 位 key;会话级 Cookie(IsPersistent=false),关闭浏览器即失效;显式退出(/api/Logout)时服务端票据立即吊销(OnSigningOut 事件,不等过期兜底)
yh:sso:Enabled true CAS SSO 开关(拦截 urlPattern / mobiApiPattern)
yh:sso:CasServerUrlPrefix - CAS 服务端地址前缀(Challenge 跳转用)
yh:app-auth:Enabled true /oapi 应用验签开关(X-App-Key / X-Timestamp / X-Signature)
yh:open:AppKey / yh:open:AppSecret 100002 OpenApi 调用方凭证

5. 待办(TODO)

  • CAS 认证:已由 Yh.Platform.Auth(CasScheme)接管,旧本地实现退役到 _legacy/Auth
  • 应用签名:AppAuthMiddleware 已接入 AppAuthVerifier + SignCanonical 真实验签
  • 用户中心:IUserCenterService 已替换为 Yh.Platform.OpenApi(BoaUserContext.Get() 取上下文)
  • ILogService 真实查询实现(替换 LogServiceStub)
  • Quartz Job 按业务启用:CleanupLogJob 已实现(每日凌晨 3:30 删除 30 天前 sys_operation_log),QuartzHostedService.InitJobs 已注册 cleanup-log-job(cron 0 30 3 * * ?)
  • 审计日志增强(SysOperationLogFilter 目前按 HTTP 方法推导级别,Module/Operation 两列恒空,可接入业务字典映射;日志查询走 LogController 但 LogServiceStub 尚未实现真实分页查询)
  • .NET 5 已 EOL:框架代码避免使用 5.0 专属 API,便于日后升 6/8