RuoVea.OmiApi.CodingRule
10.0.1.5
dotnet add package RuoVea.OmiApi.CodingRule --version 10.0.1.5
NuGet\Install-Package RuoVea.OmiApi.CodingRule -Version 10.0.1.5
<PackageReference Include="RuoVea.OmiApi.CodingRule" Version="10.0.1.5" />
<PackageVersion Include="RuoVea.OmiApi.CodingRule" Version="10.0.1.5" />
<PackageReference Include="RuoVea.OmiApi.CodingRule" />
paket add RuoVea.OmiApi.CodingRule --version 10.0.1.5
#r "nuget: RuoVea.OmiApi.CodingRule, 10.0.1.5"
#:package RuoVea.OmiApi.CodingRule@10.0.1.5
#addin nuget:?package=RuoVea.OmiApi.CodingRule&version=10.0.1.5
#tool nuget:?package=RuoVea.OmiApi.CodingRule&version=10.0.1.5
RuoVea.OmiApi.CodingRule
单据编码规则 API —— 基于 .NET 6/7/8/9/10 的轻量、跨平台编码生成组件。支持「固定值 + 日期时间 + 流水号」自由组合,一键生成 PO20240430001 这类业务单据号。内置多租户、多语言(i18n)、种子数据,配合 SqlSugar 支持 MySql / SqlServer / PostgreSQL / SQLite / Oracle 等主流数据库。
目录
业务逻辑流程说明
编码生成的完整业务逻辑(字段树、分步流程、流水号循环归零、规则级补齐/截断与示例值),见组件目录下的文档:
概览
功能矩阵
| 功能 | 说明 |
|---|---|
| 编码规则管理 | 主表 + 明细的 CRUD、批量删除、分页查询 |
| 编码生成 | GET /BasCodingRule/SN/{code}?inputChar=xxx 一行调用生成单据号 |
| 四种规则段 | Fixeds(固定值)/ RuleFixed(日期时间)/ Serial(流水号)/ InputChar(输入字符) |
| 流水号控制 | 长度、起始值、步长、补位码、溢出自动重置、按周期循环归零 |
| 长度补齐 | 规则级最大长度 + 补齐字符 + 左/右补齐 + 超出截断;分段级长度截取 |
| 生成记录 | 每次生成写入记录表,唯一约束做重复检查,提供查询接口 |
| 多租户 | 实体实现 ITenantEntity,按租户隔离 |
| 多语言 | 内置 7 种语言资源,异常消息随 Accept-Language 切换 |
| 种子数据 | 首次初始化自动写入 11 条示例规则,覆盖固定值/日期/流水号/输入字符、流水号循环、规则级补齐、禁用等各种场景 |
| 动态 WebApi | 无需 Controller,Service 方法自动暴露为 REST 端点 |
架构
┌────────────────────────────────────────────────────────┐
│ 调用方 (HTTP) │
│ GET /BasCodingRule/SN/PO ──────────┐ │
└─────────────────────────────────────────────┼──────────┘
▼
┌────────────────────────────────────────────────────────┐
│ BasCodingRuleService (动态 WebApi) │
│ GetPagesAsync / GetDataAsync / AddDataAsync / ... │
│ GetSNAsync(code) │
│ │ │
│ ├─ 1. 按 Code 查主表(含明细导航) │
│ ├─ 2. 校验:禁用?明细为空? │
│ ├─ 3. 按 Sort 拼接各规则段 → 生成 SN │
│ └─ 4. 回写 CurrentNumber / Example │
└─────────────────────────────────────────────┬──────────┘
▼
┌────────────────────────────────────────────────────────┐
│ SqlSugar (SugarRepository) │
│ BasCodingRule (主表) ──1:N── BasCodingRuleDetail │
└────────────────────────────────────────────────────────┘
安装
.NET 8.0
dotnet add package RuoVea.OmiApi.CodingRule --version 8.0.1.4
.NET 10.0
dotnet add package RuoVea.OmiApi.CodingRule --version 10.0.1.3
依赖项
| 包 | 作用 |
|---|---|
RuoVea.DynamicWebApi |
将 Service 自动映射为 REST 端点 |
RuoVea.ExSugar |
SqlSugar 仓储、UnitOfWork、CodeFirst |
RuoVea.ExDto |
PageResult / EntityBaseIdDto 等通用 DTO |
RuoVea.ExIdGen |
IdGenerator.Id 雪花 ID |
RuoVea.ExConfig |
AppSettings 静态配置读取 |
Mapster |
实体 ↔ DTO 映射 |
SqlSugarCore |
ORM(传递依赖) |
以上均为传递依赖,
dotnet add package后自动带入,无需逐个安装。
30 秒快速开始
第 1 步 —— 配置数据库连接(appsettings.json)
{
"ConnectionConfigs": [
{
"DbType": "Sqlite",
"ConnectionString": "DataSource=./ruovea.db",
"IsTenantIdFilter": true
}
],
"DbInitConfig": {
"InitTable": true,
"InitSeedData": true
}
}
| 键 | 说明 |
|---|---|
DbInitConfig:InitTable |
true 时首次启动自动 CodeFirst.InitTables 建表 |
DbInitConfig:InitSeedData |
配置项已保留;当前实现中种子数据随 InitTable 建表一起写入,本开关暂未独立生效 |
第 2 步 —— 注册服务(Program.cs)
/* 类库映射 API 组件 */
builder.Services.AddDynamicWebApi(options =>
{
options.RemoveControllerPostfixes = new List<string> { "AppService", "Service" };
options.RemovePrefix = new List<string> { "get", "post" };
});
builder.Services.AddSqlSugarSetup(); /* 扩展 ORM */
builder.Services.AddOmiCodingRuleSetup(); /* 编码规则服务 */
builder.Services.AddCodingRuleInitSetup(); /* 初始化表 + 种子数据 */
生产环境如需统一前缀,把
AddDynamicWebApi换成带options.DefaultApiPrefix = "/openapi/api"的重载即可。
第 3 步 —— 启动并验证
首次启动后组件会自动:
- 检测
BasCodingRule表是否存在 → 不存在则建BasCodingRule+BasCodingRuleDetail两张表 - 写入 11 条种子规则:基础三段式(PO/SO)、流水号循环(按年/月/天/分钟/传入字符)、输入字符段、规则级左/右补齐、高精度日期、禁用规则、前缀动态传入(DOC)等
打开 Swagger(分组 CodingRule),调用:
GET /BasCodingRule/SN/PO
返回:PO20240430001,再次调用返回 PO20240430002,依次递增。
核心场景
场景 1:创建「固定值 + 日期 + 流水号」编码规则
业务需求:采购订单号格式 PO + yyyyMMdd + 3 位流水,如 PO20240430001。
调用方 BasCodingRuleService SqlSugar
│ POST /BasCodingRule/AddData │ │
│ ────────────────────────────────▶ │ │
│ { code:"PO", details:[...] } │ 编码是否已存在? │
│ │ ─── IsAnyAsync ──▶ │
│ │ ◀── false │
│ │ InsertNav(主+明细) │
│ │ ─────────────────▶ │
│ true │ │
│ ◀──────────────────────────────── │ │
var input = new BasCodingRuleInputDto
{
Name = "采购订单编码",
Code = "PO",
IsDisable = YesOrNot.N,
BasCodingRuleDetailNav = new List<BasCodingRuleDetailInputDto>
{
new() { CodeRule = CodeRuleConst.Fixeds, FixedValue = "PO", Sort = 1 },
new() { CodeRule = CodeRuleConst.RuleFixed, FixedRule = FixedRuleConst.yyyyMMdd, Sort = 2 },
new() { CodeRule = CodeRuleConst.Serial, GlideLen = 3, GlideStep = 1,
Glide = "0", StartNumber = 1, Sort = 3 }
}
};
await service.AddDataAsync(input);
❗ 注意:Code 全局唯一,重复提交会抛出 code_exists 异常(消息随 Accept-Language 本地化)。
场景 2:生成单据编码(流水号自动递增)
调用方 BasCodingRuleService SqlSugar
│ GET /BasCodingRule/SN/PO │ │
│ ─────────────────────────────────▶│ │
│ │ 查主表 + Includes 明细 │
│ │ ────────────────────▶ │
│ │ IsDisable == Y ? │
│ │ 明细为空 ? │
│ │ │
│ │ 按 Sort 拼接: │
│ │ "PO" │
│ │ + DateTime.Now │
│ │ .ToString(...) │
│ │ + currentNumber │
│ │ .ToString("000") │
│ │ │
│ │ 回写 CurrentNumber+1 │
│ │ Example = sn │
│ │ ─── UpdateAsync ────▶ │
│ "PO20240430001" │ │
│ ◀──────────────────────────────── │ │
string sn = await service.GetSNAsync("PO");
// 第 1 次 → PO20240430001
// 第 2 次 → PO20240430002
// ...
场景 3:流水号溢出自动重置
当规则为 GlideLen = 3(最大值 10³ - 1 = 999)时:
当前值 998 → 生成 ...998 → CurrentNumber = 999
当前值 999 → 生成 ...999 → CurrentNumber = StartNumber(1) ← 溢出重置
当前值 1 → 生成 ...001 → CurrentNumber = 1 + GlideStep
源码逻辑(BasCodingRuleService.GetSNAsync):
long maxNumber = (long)Math.Pow(10, serialRule.GlideLen.Value) - 1;
int step = serialRule.GlideStep ?? 1;
codingRule.CurrentNumber = currentNumber >= maxNumber
? (serialRule.StartNumber ?? 1) // 溢出 → 回到起始值
: currentNumber + step; // 否则 → 按步长递增
❗ 注意:若明细里没有 Serial 段,则每次生成后主表 CurrentNumber 仍 +1,但该值不参与拼接。
场景 4:多规则段自由组合
规则段按 Sort 升序拼接,可任意组合:
| Sort | CodeRule | 关键字段 | 生成片段 |
|---|---|---|---|
| 1 | Fixeds | FixedValue = "INV-" |
INV- |
| 2 | RuleFixed | FixedRule = "yyyy" |
2024 |
| 3 | Fixeds | FixedValue = "-" |
- |
| 4 | RuleFixed | FixedRule = "MMdd" |
0430 |
| 5 | Serial | GlideLen=4, Glide="0" |
0007 |
生成结果:INV-2024-0430-0007
场景 5:输入字符段 —— 一条规则复用多个前缀
把前缀做成 InputChar 段,调用方通过 inputChar 传入不同前缀,即可用一条规则生成不同单据号(如 SO / PO / 任意前缀):
// 规则「通用单据编码」DOC = InputChar(前缀) + yyyyMMdd + 3 位流水
string sn1 = await service.GetSNAsync("DOC", "SO"); // → SO20260902001
string sn2 = await service.GetSNAsync("DOC", "PO"); // → PO20260902002
同理,inputChar 还可在流水号段开启 cycleMethod=INPUT_CHAR 时作为「分桶 key」,不同 inputChar 各自独立计数(与「场景 4」的固定前缀不同,前缀可动态变化)。
编码规则类型详解
四种规则类型(CodeRuleConst)
| 常量 | 值 | 说明 | 关键字段 |
|---|---|---|---|
Fixeds |
Fixeds |
固定字符串,原样拼接 | FixedValue |
RuleFixed |
RuleFixed |
日期时间,DateTime.Now.ToString(FixedRule) |
FixedRule |
Serial |
Serial |
自增流水号,支持补位 | GlideLen / StartNumber / GlideStep / Glide / CycleFlag / CycleMethod |
InputChar |
InputChar |
调用方动态传入的字符串 | 由 GetSNAsync 的 inputChar 参数提供 |
内置日期格式常量(FixedRuleConst)
| 常量 | 值 | 示例输出 |
|---|---|---|
yyyyMMdd |
yyyyMMdd | 20240430 |
yyMMdd |
yyMMdd | 240430 |
yyyyMM |
yyyyMM | 202404 |
yyyy |
yyyy | 2024 |
yy |
yy | 24 |
MMdd |
MMdd | 0430 |
yyyyMMddHHmmss |
yyyyMMddHHmmss | 20240430143045 |
yyMMddHHmmss |
yyMMddHHmmss | 240430143045 |
HHmmss |
HHmmss | 143045 |
除上述常量外,
FixedRule支持任意合法的 .NET 自定义日期格式串(如yyyy-MM-dd、dd/MM/yyyy),由DateTime.Now.ToString(format)直接解析。⚠️ 注意大小写:
HH为 24 小时制,hh为 12 小时制;MM为月份,mm为分钟。
Serial 字段说明
| 字段 | 说明 | 示例 |
|---|---|---|
GlideLen |
流水号位数,不足时用补位码在左侧补齐 | 长度 3、当前值 1 → 001 |
StartNumber |
起始值,流水号从几开始计数;溢出重置后回到该值 | 起始值 1 → 第一个编码流水位为 001 |
GlideStep |
步长,每次生成编码时流水号递增的值 | 步长 1 → 001、002、003 |
Glide |
补位码(单字符),不足设定长度时左侧补齐 | 补位码 0、长度 3、值 12 → 012 |
若
Serial段未配置GlideLen或Glide,则退化为yyyyMMddHHmmssfff时间戳拼接,保证不阻塞生成。
流水号循环归零(CycleFlag / CycleMethod)
当 CycleFlag = true 时,流水号会在指定周期结束时自动回归 StartNumber(默认 1)。周期由 CycleMethod 决定:
| 常量 | 值 | 周期 | 周期标识示例 |
|---|---|---|---|
YEAR |
1 | 按年 | 2026 |
MONTH |
2 | 按月 | 202604 |
DAY |
3 | 按天 | 20260406 |
HOUR |
4 | 按小时 | 2026040614 |
MINUTE |
5 | 按分钟 | 202604061430 |
INPUT_CHAR |
10 | 按传入字符 | inputChar 值 |
例如按天循环:当天生成
0001,次日自动归零,重新从0001开始。按传入字符循环时,不同inputChar各自独立计数。注意:主表「当前周期标识」CurrentCycle仅记录最近一次周期,切回旧周期会从起始值重新累计(单桶连续使用场景下正确)。
长度补齐与截断
- 分段级:每个分段的
Length会截取超长内容(补零位数仍由流水号的GlideLen决定)。 - 规则级:
MaxLength+Padded+PaddedChar+PaddedMethod。启用补齐(Padded=true)时按补齐字符与左/右方式填充至最大长度(默认左补齐、补0);未启用补齐但超出MaxLength时直接截断。
生成记录与重复检查
每次生成会写入 BasCodingRuleRecord 表(记录最终编码 Result、流水号 SerialNo、传入参数 InputChar),其 Result 唯一约束用于重复检查——生成的编码重复时抛出 code_duplicate。历史记录可通过记录服务 BasCodingRuleRecordService 的查询接口检索。
配置选项详解
DbInitConfig
| 键 | 类型 | 默认 | 说明 |
|---|---|---|---|
InitTable |
bool | false | 启动时检测并自动建表(含种子数据写入) |
InitSeedData |
bool | false | 是否启用种子数据(当前实现中种子数据随 InitTable 一起写入,此开关在本组件内暂未独立判断) |
ConnectionConfigs
| 键 | 说明 |
|---|---|
DbType |
MySql / SqlServer / Sqlite / Oracle / PostgreSQL / Dm 等 |
ConnectionString |
数据库连接字符串 |
IsTenantIdFilter |
多租户过滤开关,编码规则实体实现了 ITenantEntity,建议开启 |
IsDeleteFilter |
软删除过滤,实体继承 EntityBase,建议开启 |
DI 注册三种重载
// 1. 从 AppSettings.Configuration 的 "DbInitConfig" 节点读取
services.AddOmiCodingRuleSetup();
// 2. 显式传入 IConfiguration 节点
services.AddOmiCodingRuleSetup(configuration.GetSection("DbInitConfig"));
// 3. 代码内联配置
services.AddOmiCodingRuleSetup(c => { c.InitTable = true; c.InitSeedData = true; });
三种重载均支持可选参数 ServiceLifetime(默认 Scoped)。
服务生命周期
| 服务 | 默认生命周期 | 说明 |
|---|---|---|
BasCodingRuleService |
Scoped | 编码规则主表服务 |
BasCodingRuleDetailService |
Scoped | 编码规则明细服务 |
API 接口速览
动态 WebApi 路由规则:去掉
Service后缀作为控制器名,方法名去掉Get/Post前缀后按Async之前的动词映射 HTTP 方法。生产环境若配置了DefaultApiPrefix = "/openapi/api",则所有路由前加该前缀。
BasCodingRuleService(编码规则)
| HTTP | 路由 | 方法 | 说明 |
|---|---|---|---|
| GET | /BasCodingRule/Pages |
GetPagesAsync |
分页查询(含明细导航) |
| GET | /BasCodingRule/Data |
GetDataAsync |
按 Id 查详情(含明细) |
| GET | /BasCodingRule/List |
GetListAsync |
全部未删除列表 |
| POST | /BasCodingRule/AddData |
AddDataAsync |
新增(主表 + 明细,事务) |
| POST | /BasCodingRule/UpdateData |
UpdateDataAsync |
更新(主表 + 明细,事务) |
| DELETE | /BasCodingRule/Data |
DeleteDataAsync |
删除(级联明细,事务) |
| POST | /BasCodingRule/DeleteBatch |
DeleteBatchAsync |
批量删除(级联明细,事务) |
| GET | /BasCodingRule/SN/{code} |
GetSNAsync |
生成单据编码(事务 + 差异日志) |
BasCodingRuleDetailService(编码规则明细)
| HTTP | 路由 | 方法 | 说明 |
|---|---|---|---|
| GET | /BasCodingRuleDetail/Pages |
GetPagesAsync |
分页查询 |
| GET | /BasCodingRuleDetail/Data |
GetDataAsync |
按 Id 查详情 |
| GET | /BasCodingRuleDetail/List |
GetListAsync |
按 codingRuleId 查明细列表(按 Sort 排序) |
| POST | /BasCodingRuleDetail/AddData |
AddDataAsync |
新增明细(事务) |
| POST | /BasCodingRuleDetail/UpdateData |
UpdateDataAsync |
更新明细(事务) |
| DELETE | /BasCodingRuleDetail/Data |
DeleteDataAsync |
删除明细 |
| POST | /BasCodingRuleDetail/DeleteBatch |
DeleteBatchAsync |
批量删除明细(事务) |
所有接口均带
[DisplayName]/[Description]特性,便于 Swagger 文档生成与操作日志记录。
错误处理与日志
i18n 错误码一览
| 资源键 | 中文消息 | 触发场景 |
|---|---|---|
code_exists |
编码已存在 | 新增 / 更新时 Code 与其他记录重复 |
record_not_exist |
记录不存在 | 更新时按 Id 找不到记录 |
select_delete_records |
请选择要删除的记录 | 批量删除传入空数组 |
code_required |
编码规则编码不能为空 | GetSNAsync 传入空白 code |
rule_not_found |
未找到编码规则 | GetSNAsync 按 code 查不到记录 |
rule_disabled |
编码规则已禁用 | IsDisable == Y 仍调用 GetSNAsync |
rule_detail_empty |
编码规则未配置明细 | 主表无任何明细段时调用 GetSNAsync |
调用方错误处理示例
try
{
string sn = await service.GetSNAsync("PO");
}
catch (Exception ex)
{
// 消息已按请求的 Accept-Language 自动本地化
logger.LogWarning("生成编码失败: {Message}", ex.Message);
throw;
}
生成编码的更新操作通过
UpdateAsync(codingRule, enableDiffLog: true, businessData: "生成单据编码")写入差异日志,可在审计日志中追溯每次 SN 变化。
多语言(i18n)
组件内置 ResX 资源文件(Language/i18n.*.resx),异常消息自动按请求的 Accept-Language 头返回对应语言。
| 语言 | Culture |
|---|---|
| 简体中文(默认) | zh-CN |
| 繁体中文-香港 | zh-HK |
| 繁体中文-台湾 | zh-TW |
| 英语 | en-US |
| 日语 | ja-JP |
| 法语 | fr-FR |
| 越南语 | vi-VN |
代码中通过强类型静态类访问:
using RuoVea.OmiApi.CodingRule.Language;
throw new Exception(i18n.code_exists);
常见问题
⚠️ Q1:首次启动没有自动建表?
检查 appsettings.json 中 DbInitConfig:InitTable 是否为 true,并确认已调用 AddCodingRuleInitSetup()。建表在 Task.Run 中异步执行,首次启动后稍等片刻表即就绪。
⚠️ Q2:调用 /BasCodingRule/SN/PO 提示「未找到编码规则」?
确认:① 表已建好且种子数据已写入;② 传入的 code 与主表 Code 字段完全一致(区分大小写取决于数据库排序规则);③ 记录未被软删除。
❗ Q3:流水号到了 999 会怎样?
自动重置为 StartNumber(默认 1),见「场景 3」。若业务不允许重置,请加大 GlideLen。
❗ Q4:日期格式用 hh 还是 HH?
HH = 24 小时制(推荐),hh = 12 小时制。MM = 月份,mm = 分钟,别写反。
⚠️ Q5:生产环境路由 404?
生产环境通常配置了 DefaultApiPrefix = "/openapi/api",完整路由应为 /openapi/api/BasCodingRule/SN/PO。
⚠️ Q6:更新主表时部分字段被置 null?
更新走 UpdateNav + IsIgnoreAllNullColumns = true,传 null 的字段不会被覆盖;但明细中想清空的字段请传空字符串而非 null。
❗ Q7:多租户下查不到数据?
实体带 [Tenant("0")] 且实现 ITenantEntity,请确认 ConnectionConfigs 中 IsTenantIdFilter: true 且当前请求租户与种子数据租户(SqlSugarConst.DefaultTenantId)一致。
许可证
相关包
| 包 | 说明 |
|---|---|
| RuoVea.DynamicWebApi | 动态 WebApi |
| RuoVea.ExSugar | SqlSugar 扩展 |
| RuoVea.ExDto | 通用 DTO |
| RuoVea.ExIdGen | 分布式 ID |
| RuoVea.ExConfig | 配置读取 |
| RuoVea.OmiApi.Dict | 数据字典模块 |
| RuoVea.OmiApi.Log | 系统日志模块 |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- RuoVea.DynamicWebApi (>= 10.0.0)
- RuoVea.ExSugar (>= 10.0.1.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.1.5 | 93 | 9/2/2026 |
| 10.0.1.4 | 98 | 8/20/2026 |
| 10.0.1.3 | 102 | 8/16/2026 |
| 10.0.1.2 | 94 | 8/15/2026 |
| 10.0.1.1 | 101 | 8/15/2026 |
| 10.0.1 | 101 | 8/14/2026 |
| 10.0.0.2 | 106 | 7/24/2026 |
| 10.0.0.1 | 108 | 7/21/2026 |
| 8.0.1.6 | 92 | 9/2/2026 |
| 8.0.1.5 | 103 | 8/20/2026 |
| 8.0.1.4 | 95 | 8/18/2026 |
| 8.0.1.3 | 103 | 8/16/2026 |
| 8.0.1.2 | 94 | 8/15/2026 |
| 8.0.1.1 | 94 | 8/15/2026 |
| 8.0.1 | 103 | 8/14/2026 |
| 8.0.0.2 | 108 | 7/24/2026 |
| 8.0.0.1 | 104 | 7/21/2026 |
| 6.0.0.3 | 99 | 8/18/2026 |
| 6.0.0.2 | 102 | 7/24/2026 |
| 6.0.0.1 | 105 | 7/21/2026 |