7.9 KiB
7.9 KiB
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-authAppKey/AppSecret)→ Redissign: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(cron0 30 3 * * ?) - 审计日志增强(
SysOperationLogFilter目前按 HTTP 方法推导级别,Module/Operation两列恒空,可接入业务字典映射;日志查询走LogController但LogServiceStub尚未实现真实分页查询) - .NET 5 已 EOL:框架代码避免使用 5.0 专属 API,便于日后升 6/8