feat: initialize Evergreen Health backend
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"APIJSON": {
|
||||
"Roles": [
|
||||
{
|
||||
"RoleName": "Role1", // 权限名称 唯一
|
||||
"Select": { // 查询
|
||||
"Table": [ "*" ], // 可操作的表
|
||||
"Column": [ "*" ], // 可操作的字段
|
||||
"Filter": []
|
||||
},
|
||||
"Insert": { // 添加
|
||||
"Table": [ "table1", "table2", "table3" ],
|
||||
"Column": [ "*", "*", "tb.*" ]
|
||||
},
|
||||
"Update": { // 修改
|
||||
"Table": [ "table1", "table2", "table3" ],
|
||||
"Column": [ "*", "tb.*", "tb.*" ]
|
||||
},
|
||||
"Delete": { // 删除
|
||||
"Table": [ "table1", "table2", "table3" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"RoleName": "Role2",
|
||||
"Select": {
|
||||
"Table": [ "table1" ],
|
||||
"Column": [ "tb.*" ]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// 支付宝支付配置,文档地址:https://openhome.alipay.com/develop/sandbox/app
|
||||
"Alipay": {
|
||||
"ServerUrl": "https://openapi-sandbox.dl.alipaydev.com/gateway.do", // 支付宝网关地址
|
||||
"WebsocketUrl": "openchannel-sandbox.dl.alipaydev.com", // websocket服务地址
|
||||
//"AuthUrl": "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm", // 正式环境授权回调地址
|
||||
"AuthUrl": "https://openauth-sandbox.dl.alipaydev.com/oauth2/publicAppAuthorize.htm", // 沙箱环境授权回调地址
|
||||
"AppAuthUrl": "http://xxxxxxxxxx", // 应用授权回调地址
|
||||
"NotifyUrl": "http://xxxxxxxxx/api/sysAlipay/Notify", // 应用网关地址
|
||||
"RootCertPath": "AlipayCrt/alipayRootCert.crt", // 支付宝根证书存放路径
|
||||
"AccountList": [
|
||||
{
|
||||
"Name": "sandbox 默认应用",
|
||||
"AppId": "xxxxxxxxxxxxxx",
|
||||
"SignType": "RSA2",
|
||||
"PrivateKey": "xxxxxxxxxxxxxxxxx",
|
||||
"EncryptKey": "xxxxxxxxxxxxxxxxxxxx",
|
||||
"AppCertPath": "AlipayCrt/appPublicCert.crt", // 应用公钥证书存放路径
|
||||
"AlipayPublicCertPath": "AlipayCrt/alipayPublicCert.crt" // 支付宝公钥证书存放路径
|
||||
},
|
||||
{
|
||||
"Name": "sandbox 默认应用2",
|
||||
"AppId": "xxxxxxxxxxxxxx",
|
||||
"SignType": "RSA2",
|
||||
"PrivateKey": "xxxxxxxxxxxxxxxxx",
|
||||
"EncryptKey": "xxxxxxxxxxxxxxxxxxxx",
|
||||
"AppCertPath": "AlipayCrt/appPublicCert.crt", // 应用公钥证书存放路径
|
||||
"AlipayPublicCertPath": "AlipayCrt/alipayPublicCert.crt" // 支付宝公钥证书存放路径
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"Urls": "http://*:5005", // 默认端口
|
||||
"AllowedHosts": "*", // 允许所有地址
|
||||
|
||||
"AppSettings": {
|
||||
"InjectSpecificationDocument": true, // 生产环境是否开启Swagger
|
||||
"ExternalAssemblies": [ "plugins" ], // 插件目录
|
||||
"VirtualPath": "" // 二级虚拟目录
|
||||
},
|
||||
"DynamicApiControllerSettings": {
|
||||
//"DefaultRoutePrefix": "api", // 默认路由前缀
|
||||
"CamelCaseSeparator": "", // 驼峰命名分隔符
|
||||
"SplitCamelCase": false, // 切割骆驼(驼峰)/帕斯卡命名
|
||||
"LowercaseRoute": false, // 小写路由格式
|
||||
"AsLowerCamelCase": true, // 小驼峰命名(首字母小写)
|
||||
"KeepVerb": false, // 保留动作方法请求谓词
|
||||
"KeepName": false // 保持原有名称不处理
|
||||
},
|
||||
"FriendlyExceptionSettings": {
|
||||
"DefaultErrorMessage": "系统异常,请联系管理员",
|
||||
"ThrowBah": true, // 是否将 Oops.Oh 默认抛出为业务异常
|
||||
"LogError": false // 是否输出异常日志
|
||||
},
|
||||
// 静态资源处理方式(允许这些文件被访问),包含".*": "application/octet-stream"允许访问所有静态资源
|
||||
"StaticContentTypeMappings": {
|
||||
".dll": "application/octet-stream",
|
||||
".exe": "application/octet-stream",
|
||||
".pdb": "application/octet-stream",
|
||||
".so": "application/octet-stream"
|
||||
},
|
||||
"LocalizationSettings": {
|
||||
"SupportedCultures": [ "zh-CN", "en" ], // 语言列表
|
||||
"DefaultCulture": "zh-CN", // 默认语言
|
||||
"DateTimeFormatCulture": "zh-CN" // 固定时间区域为特定时区(多语言)
|
||||
},
|
||||
"CorsAccessorSettings": {
|
||||
//"PolicyName": "App.Cors.Policy", // 跨域策略名称
|
||||
//"WithOrigins": [ "http://localhost:5005", "https://gitee.com" ], // 允许的跨域地址
|
||||
"WithExposedHeaders": [ "Content-Disposition", "X-Pagination", "access-token", "x-access-token", "Access-Control-Expose-Headersx-access-token" ], // 如果前端不代理且是axios请求
|
||||
"SignalRSupport": true // 启用 SignalR 跨域支持
|
||||
},
|
||||
// 定时任务/作业调度
|
||||
"JobSchedule": {
|
||||
"Enabled": true // 是否开启
|
||||
},
|
||||
// 雪花Id
|
||||
"SnowId": {
|
||||
"WorkerId": 1, // 雪花Id机器码,多服务器时全局唯一
|
||||
"WorkerIdBitLength": 6, // 机器码位长 默认值6,取值范围 [1, 19]
|
||||
"SeqBitLength": 6, // 序列数位长 默认值6,取值范围 [3, 21](建议不小于4,值越大性能越高、Id位数也更长)
|
||||
"WorkerPrefix": "adminnet_" // 缓存前缀
|
||||
},
|
||||
// 密码策略
|
||||
"Cryptogram": {
|
||||
"StrongPassword": false, // 是否开启密码强度验证
|
||||
"PasswordStrengthValidation": "(?=^.{6,16}$)(?=.*\\d)(?=.*\\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\\n).*$", // 密码强度验证正则表达式,必须须包含大小写字母、数字和特殊字符的组合,长度在6-16之间
|
||||
"PasswordStrengthValidationMsg": "密码必须包含大小写字母、数字和特殊字符的组合,长度在6-16之间", // 密码强度验证消息提示
|
||||
"CryptoType": "SM2", // 密码加密算法:MD5、SM2、SM4
|
||||
// 新业务系统记得改密匙,通过接口(http://localhost:5005/api/sysCommon/smKeyPair)获取。记得同步修改前端公钥配置:VITE_SM_PUBLIC_KEY
|
||||
"PublicKey": "0484C7466D950E120E5ECE5DD85D0C90EAA85081A3A2BD7C57AE6DC822EFCCBD66620C67B0103FC8DD280E36C3B282977B722AAEC3C56518EDCEBAFB72C5A05312", // 公钥
|
||||
"PrivateKey": "8EDB615B1D48B8BE188FC0F18EC08A41DF50EA731FA28BF409E6552809E3A111" // 私钥
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"CDConfig": {
|
||||
"Enabled": true, // 启用持续部署
|
||||
"Owner": "zuohuaijun", // gitee用户名
|
||||
"Repo": "Admin.NET", // 仓库名
|
||||
"Branch": "v2", // 分支名
|
||||
"AccessToken": "xxxxxxxxxxxxxxxxxxxxxxxxx", // gitee用户授权码
|
||||
"UpdateInterval": 0, // 最小更新间隔(分钟),0不限制
|
||||
"BackupCount": 10, // 备份文件保留数量,0不限制
|
||||
"BackendOutput": "D:\\Admin.NET", // 后端输出目录
|
||||
"Publish": { // 后端发布选项
|
||||
"Configuration": "Release", // 发布运行版本
|
||||
"TargetFramework": "net8.0", // 发布.NET版本
|
||||
"RuntimeIdentifier": "linux-x64" // 运行平台
|
||||
},
|
||||
"ExcludeFiles": [ "Configuration\\*.json" ] // 排除文件
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// 缓存配置
|
||||
"Cache": {
|
||||
"Prefix": "adminnet_", // 全局缓存前缀
|
||||
"CacheType": "Memory", // Memory、Redis
|
||||
"Redis": {
|
||||
"Configuration": "server=127.0.0.1:6379;password=;db=5;", // Redis连接字符串
|
||||
"Prefix": "adminnet_", // Redis前缀(目前没用)
|
||||
"MaxMessageSize": "1048576", // 最大消息大小 默认1024 * 1024
|
||||
"AutoDetect": false // 自动检测集群节点 阿里云的Redis分布式集群使用代理模式,需要设置为false关闭自动检测;如果不用代理地址,就配置多个节点地址并打开自动检测
|
||||
}
|
||||
},
|
||||
// 集群配置
|
||||
"Cluster": {
|
||||
"Enabled": false, // 启用集群:前提开启Redis缓存模式
|
||||
"ServerId": "adminnet", // 服务器标识
|
||||
"ServerIp": "", // 服务器IP
|
||||
"SignalR": {
|
||||
"RedisConfiguration": "127.0.0.1:6379,ssl=false,password=,defaultDatabase=5",
|
||||
"ChannelPrefix": "signalrPrefix_"
|
||||
},
|
||||
"DataProtecteKey": "AdminNet:DataProtection-Keys",
|
||||
"IsSentinel": false, // 是否哨兵模式
|
||||
"SentinelConfig": {
|
||||
"DefaultDb": "4",
|
||||
"EndPoints": [ // 哨兵端口
|
||||
// "10.10.0.124:26380"
|
||||
],
|
||||
"MainPrefix": "adminNet:",
|
||||
"Password": "123456",
|
||||
"SentinelPassword": "adminNet",
|
||||
"ServiceName": "adminNet",
|
||||
"SignalRChannelPrefix": "signalR:"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// Lazy.Captcha.Core 组件详细文档(https://api.gitee.com/pojianbing/lazy-captcha/)
|
||||
"CaptchaOptions": {
|
||||
"CaptchaType": 10, // 验证码类型0、1、2、3、4、5、6、7、8、9、10、11
|
||||
"CodeLength": 1, // 验证码长度, 要放在CaptchaType设置后 当类型为算术表达式时,长度代表操作的个数, 例如2
|
||||
"ExpirySeconds": 60, // 验证码过期秒数
|
||||
"IgnoreCase": true, // 比较时是否忽略大小写
|
||||
"StoreageKeyPrefix": "", // 存储键前缀
|
||||
"ImageOption": {
|
||||
"Animation": true, // 是否启用动画
|
||||
"FontSize": 36, // 字体大小
|
||||
"Width": 150, // 验证码宽度
|
||||
"Height": 50, // 验证码高度
|
||||
"BubbleMinRadius": 5, // 气泡最小半径
|
||||
"BubbleMaxRadius": 10, // 气泡最大半径
|
||||
"BubbleCount": 3, // 气泡数量
|
||||
"BubbleThickness": 1.0, // 气泡边沿厚度
|
||||
"InterferenceLineCount": 3, // 干扰线数量
|
||||
"FontFamily": "kaiti", // 包含actionj,epilog,fresnel,headache,lexo,prefix,progbot,ransom,robot,scandal,kaiti
|
||||
"FrameDelay": 300, // 每帧延迟,Animation=true时有效, 默认300
|
||||
"BackgroundColor": "#ffffff", // 格式: rgb, rgba, rrggbb, or rrggbbaa format to match web syntax, 默认#fff
|
||||
"ForegroundColors": "", // 颜色格式同BackgroundColor,多个颜色逗号分割,随机选取。不填,空值,则使用默认颜色集
|
||||
"Quality": 100, // 图片质量(质量越高图片越大,gif调整无效可能会更大)
|
||||
"TextBold": true // 粗体
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// 代码生成配置项-程序集名称集合
|
||||
"CodeGen": {
|
||||
"EntityAssemblyNames": [ "Admin.NET.Core", "Evergreen.Health.Application" ], // 实体所在程序集(自行添加新建程序集名称)
|
||||
"BaseEntityNames": [ "EntityBaseId", "EntityBase", "EntityBaseDel", "EntityBaseOrg", "EntityBaseOrgDel", "EntityBaseTenant", "EntityBaseTenantDel", "EntityBaseTenantId", "EntityBaseTenantOrg", "EntityBaseTenantOrgDel" ], // 实体基类名称
|
||||
"FrontRootPath": "Web", // 前端项目根目录
|
||||
"BackendApplicationNamespaces": [ "Evergreen.Health.Application" ] // 后端生成到的项目(自行添加新建命名空间)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// 详细数据库配置见SqlSugar官网(第一个为默认库),极力推荐 PostgreSQL 数据库
|
||||
// 数据库连接字符串参考地址:https://www.connectionstrings.com/
|
||||
"DbConnection": {
|
||||
"EnableConsoleSql": false, // 启用控制台打印SQL
|
||||
"ConnectionConfigs": [
|
||||
{
|
||||
//"ConfigId": "1300000000001", // 默认库标识-禁止修改
|
||||
"DbType": "MySqlConnector", // MySql、SqlServer、Sqlite、Oracle、PostgreSQL、Dm、Kdbndp、Oscar、MySqlConnector、Access、OpenGauss、QuestDB、HG、ClickHouse、GBase、Odbc、Custom
|
||||
"DbNickName": "系统库",
|
||||
"ConnectionString": "Server=127.0.0.1;Port=3306;Database=evergreen_health;Uid=root;Pwd=CHANGE_ME;SslMode=None;AllowLoadLocalInfile=true;AllowUserVariables=true;CharSet=utf8mb4;", // 本地开发MySQL:请替换Uid和Pwd
|
||||
//"ConnectionString": "PORT=5432;DATABASE=xxx;HOST=localhost;PASSWORD=xxx;USER ID=xxx", // PostgreSQL
|
||||
//"ConnectionString": "Server=localhost;Database=xxx;Uid=xxx;Pwd=xxx;SslMode=None;AllowLoadLocalInfile=true;AllowUserVariables=true;", // MySql,
|
||||
//"ConnectionString": "User Id=xxx; Password=xxx; Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)))", // Oracle
|
||||
//"ConnectionString": "Server=localhost;Database=xxx;User Id=xxx;Password=xxx;Encrypt=True;TrustServerCertificate=True;", // SqlServer
|
||||
|
||||
//"SlaveConnectionConfigs": [ // 读写分离/主从
|
||||
// {
|
||||
// "HitRate": 10,
|
||||
// "ConnectionString": "DataSource=./Admin.NET1.db"
|
||||
// },
|
||||
// {
|
||||
// "HitRate": 10,
|
||||
// "ConnectionString": "DataSource=./Admin.NET2.db"
|
||||
// }
|
||||
//],
|
||||
"DbSettings": {
|
||||
"EnableInitDb": true, // 启用库初始化(若实体没有变化建议关闭)
|
||||
"EnableInitView": true, // 启用视图初始化(若实体和视图没有变化建议关闭)
|
||||
"EnableDiffLog": false, // 启用库表差异日志
|
||||
"EnableUnderLine": false, // 启用驼峰转下划线
|
||||
"EnableConnEncrypt": false // 启用数据库连接串加密(国密SM2加解密)
|
||||
},
|
||||
"TableSettings": {
|
||||
"EnableInitTable": true, // 启用表初始化(若实体没有变化建议关闭)
|
||||
"EnableIncreTable": false // 启用表增量更新(只更新贴了特性[IncreTable]的实体表)
|
||||
},
|
||||
"SeedSettings": {
|
||||
"EnableInitSeed": true, // 启用种子初始化(若种子没有变化建议关闭)
|
||||
"EnableIncreSeed": false // 启用种子增量更新(只更新贴了特性[IncreSeed]的种子表)
|
||||
}
|
||||
}
|
||||
//// 日志独立数据库配置
|
||||
//{
|
||||
// "ConfigId": "1300000000002", // 日志库标识-禁止修改
|
||||
// "DbNickName": "日志库",
|
||||
// "DbType": "Sqlite",
|
||||
// "ConnectionString": "DataSource=./Admin.NET.Log.db", // 库连接字符串
|
||||
// "DbSettings": {
|
||||
// "EnableInitDb": true, // 启用库初始化(若实体没有变化建议关闭)
|
||||
// "EnableDiffLog": false, // 启用库表差异日志
|
||||
// "EnableUnderLine": false // 启用驼峰转下划线
|
||||
// },
|
||||
// "TableSettings": {
|
||||
// "EnableInitTable": true, // 启用表初始化(若实体没有变化建议关闭)
|
||||
// "EnableIncreTable": false // 启用表增量更新(只更新贴了特性[IncreTable]的实体表)
|
||||
// },
|
||||
// "SeedSettings": {
|
||||
// "EnableInitSeed": false, // 启用种子初始化(若种子没有变化建议关闭)
|
||||
// "EnableIncreSeed": false // 启用种子增量更新(只更新贴了特性[IncreSeed]的种子表)
|
||||
// }
|
||||
//},
|
||||
//// 其他数据库配置(可以配置多个)
|
||||
//{
|
||||
// "ConfigId": "test", // 库标识
|
||||
// "DbType": "Sqlite", // 库类型
|
||||
// "ConnectionString": "DataSource=./Admin.NET.Test.db", // 库连接字符串
|
||||
// "DbSettings": {
|
||||
// "EnableInitDb": true, // 启用库初始化(若实体没有变化建议关闭)
|
||||
// "EnableDiffLog": false, // 启用库表差异日志
|
||||
// "EnableUnderLine": false // 启用驼峰转下划线
|
||||
// },
|
||||
// "TableSettings": {
|
||||
// "EnableInitTable": true, // 启用表初始化(若实体没有变化建议关闭)
|
||||
// "EnableIncreTable": false // 启用表增量更新(只更新贴了特性[IncreTable]的实体表)
|
||||
// },
|
||||
// "SeedSettings": {
|
||||
// "EnableInitSeed": true, // 启用种子初始化(若种子没有变化建议关闭)
|
||||
// "EnableIncreSeed": false // 启用种子增量更新(只更新贴了特性[IncreSeed]的种子表)
|
||||
// }
|
||||
//}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"DeepSeekSettings": {
|
||||
"SourceLang": "zh-cn",
|
||||
"ApiUrl": "https://api.deepseek.com/v1/chat/completions",
|
||||
"ApiKey": "你的 API KEY"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"ElasticSearch:Logging": {
|
||||
"Enabled": true, // 启用ES日志
|
||||
"AuthType": "None", // ES认证类型,可选 Basic、ApiKey、Base64ApiKey、None
|
||||
"User": "elastic", // Basic认证的用户名,使用Basic认证类型时必填
|
||||
"Password": "123456", // Basic认证的密码,使用Basic认证类型时必填
|
||||
"ApiId": "", // 使用ApiKey认证类型时必填
|
||||
"ApiKey": "", // 使用ApiKey认证类型时必填
|
||||
"Base64ApiKey": "TmtrOEszNEJuQ0NyaWlydGtROFk6SG1RZ0w3YzBTc2lCanJTYlV3aXNzZw==", // 使用Base64ApiKey认证类型时必填
|
||||
"Fingerprint": "37:08:6A:C6:06:CC:9A:43:CF:ED:25:A2:1C:A4:69:57:90:31:2C:06:CA:61:56:39:6A:9C:46:11:BD:22:51:DA", // ES使用Https时的证书指纹
|
||||
"ServerUris": [ "http://192.168.3.90:9200" ], // 地址
|
||||
"DefaultIndex": "adminnet" // 索引
|
||||
},
|
||||
"ElasticSearch:Business": {
|
||||
"Enabled": false, // 启用ES日志
|
||||
"AuthType": "Basic", // ES认证类型,可选 Basic、ApiKey、Base64ApiKey、None
|
||||
"User": "admin", // Basic认证的用户名,使用Basic认证类型时必填
|
||||
"Password": "123456", // Basic认证的密码,使用Basic认证类型时必填
|
||||
"ApiId": "", // 使用ApiKey认证类型时必填
|
||||
"ApiKey": "", // 使用ApiKey认证类型时必填
|
||||
"Base64ApiKey": "TmtrOEszNEJuQ0NyaWlydGtROFk6SG1RZ0w3YzBTc2lCanJTYlV3aXNzZw==", // 使用Base64ApiKey认证类型时必填
|
||||
"Fingerprint": "37:08:6A:C6:06:CC:9A:43:CF:ED:25:A2:1C:A4:69:57:90:31:2C:06:CA:61:56:39:6A:9C:46:11:BD:22:51:DA", // ES使用Https时的证书指纹
|
||||
"ServerUris": [ "http://192.168.1.100:9200" ], // 地址
|
||||
"DefaultIndex": "adminnet" // 索引
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// 邮箱配置
|
||||
"Email": {
|
||||
"Host": "smtp.163.com", // 主机
|
||||
"Port": 465, // 端口 465、994、25
|
||||
"EnableSsl": true, // 启用SSL
|
||||
"DefaultFromEmail": "xxx@163.com", // 默认发件者邮箱
|
||||
"DefaultToEmail": "xxx@qq.com", // 默认接收人邮箱
|
||||
"UserName": "xxx@163.com", // 邮箱账号
|
||||
"Password": "", // 邮箱授权码
|
||||
"DefaultFromName": "常青树健康管理系统" // 默认邮件标题
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// 枚举配置(将所有枚举转成字典)
|
||||
"Enum": {
|
||||
"EntityAssemblyNames": [ "Admin." ] // 枚举所在程序集(自行添加新建程序集名称)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"EventBus": {
|
||||
// 事件源存储器类型,默认内存存储(Redis则需要配合缓存相关配置)
|
||||
"EventSourceType": "Memory", // Memory、Redis、RabbitMQ、Kafka
|
||||
"RabbitMQ": {
|
||||
"UserName": "adminnet",
|
||||
"Password": "adminnet++123456",
|
||||
"HostName": "127.0.0.1",
|
||||
"Port": 5672
|
||||
},
|
||||
"Kafka": {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"JWTSettings": {
|
||||
"ValidateIssuerSigningKey": true, // 是否验证密钥,bool 类型,默认true
|
||||
"IssuerSigningKey": "3c1cbc3f546eda35168c3aa3cb91780fbe703f0996c6d123ea96dc85c70bbc0a", // 密钥,string 类型,必须是复杂密钥,长度大于16
|
||||
"ValidateIssuer": true, // 是否验证签发方,bool 类型,默认true
|
||||
"ValidIssuer": "Admin.NET", // 签发方,string 类型
|
||||
"ValidateAudience": true, // 是否验证签收方,bool 类型,默认true
|
||||
"ValidAudience": "Admin.NET", // 签收方,string 类型
|
||||
"ValidateLifetime": true, // 是否验证过期时间,bool 类型,默认true,建议true
|
||||
//"ExpiredTime": 20, // 过期时间,long 类型,单位分钟,默认20分钟,最大支持 13 年
|
||||
"ClockSkew": 5, // 过期时间容错值,long 类型,单位秒,默认5秒
|
||||
"Algorithm": "HS256", // 加密算法,string 类型,默认 HS256
|
||||
"RequireExpirationTime": true // 验证过期时间,设置 false 将永不过期
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// IP限流配置
|
||||
"IpRateLimiting": {
|
||||
// 例如:设置每分钟5次访问限流
|
||||
// 当False时:每个接口都加入计数,不管你访问哪个接口,只要在一分钟内累计够5次,将禁止访问。
|
||||
// 当True 时:当一分钟请求了5次GetData接口,则该接口将在时间段内禁止访问,但是还可以访问PostData()5次,总得来说是每个接口都有5次在这一分钟,互不干扰。
|
||||
"EnableEndpointRateLimiting": true,
|
||||
// 如果StackBlockedRequests设置为false,拒绝的API调用不会添加到调用次数计数器上。比如:如果客户端每秒发出3个请求并且您设置了每秒一个调用的限制,
|
||||
// 则每分钟或每天计数器等其他限制将仅记录第一个调用,即成功的API调用。如果您希望被拒绝的API调用计入其他时间的显示(分钟,小时等),则必须设置
|
||||
"StackBlockedRequests": false,
|
||||
// 在RealIpHeader使用时,你的Kestrel服务器背后是一个反向代理,如果你的代理服务器使用不同的页眉然后提取客户端IP X-Real-IP使用此选项来设置它。
|
||||
"RealIpHeader": "X-Real-IP",
|
||||
// 将ClientIdHeader被用于提取白名单的客户端ID。如果此标头中存在客户端ID并且与ClientWhitelist中指定的值匹配,则不应用速率限制。
|
||||
"ClientIdHeader": "X-ClientId",
|
||||
// IP白名单:支持Ipv4和Ipv6
|
||||
"IpWhitelist": [],
|
||||
// 端点白名单
|
||||
"EndpointWhitelist": [],
|
||||
// 客户端白名单
|
||||
"ClientWhitelist": [],
|
||||
"QuotaExceededResponse": {
|
||||
"Content": "{{\"code\":429,\"type\":\"error\",\"message\":\"访问过于频繁,请稍后重试!禁止违法行为否则110 👮\",\"result\":null,\"extras\":null}}",
|
||||
"ContentType": "application/json",
|
||||
"StatusCode": 429
|
||||
},
|
||||
// 返回状态码
|
||||
"HttpStatusCode": 429,
|
||||
// API规则,结尾一定要带*
|
||||
"GeneralRules": [
|
||||
// 1秒钟只能调用1000次
|
||||
{
|
||||
"Endpoint": "*",
|
||||
"Period": "1s",
|
||||
"Limit": 1000
|
||||
},
|
||||
// 1分钟只能调用60000次
|
||||
{
|
||||
"Endpoint": "*",
|
||||
"Period": "1m",
|
||||
"Limit": 60000
|
||||
}
|
||||
//// 1小时只能调用3600000次
|
||||
//{
|
||||
// "Endpoint": "*",
|
||||
// "Period": "1h",
|
||||
// "Limit": 3600000
|
||||
//},
|
||||
//// 1天只能调用86400000次
|
||||
//{
|
||||
// "Endpoint": "*",
|
||||
// "Period": "1d",
|
||||
// "Limit": 86400000
|
||||
//}
|
||||
]
|
||||
},
|
||||
// IP 黑名单
|
||||
"IpRateLimitPolicies": {
|
||||
"IpRules": [
|
||||
{
|
||||
"Ip": "0.0.0.0", // IP可用:"::1/10"
|
||||
"Rules": [
|
||||
{
|
||||
"Endpoint": "*",
|
||||
"Period": "1s",
|
||||
"Limit": 0 // 设置为0就是1次都不能请求,完全屏蔽
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// 客户端限流配置
|
||||
"ClientRateLimiting": {
|
||||
"EnableEndpointRateLimiting": true,
|
||||
"ClientIdHeader": "X-ClientId",
|
||||
"EndpointWhitelist": [],
|
||||
"ClientWhitelist": [],
|
||||
"QuotaExceededResponse": {
|
||||
"Content": "{{\"code\":429,\"type\":\"error\",\"message\":\"访问人数过多,请稍后重试!\",\"result\":null,\"extras\":null}}",
|
||||
"ContentType": "application/json",
|
||||
"StatusCode": 429
|
||||
},
|
||||
"HttpStatusCode": 429,
|
||||
"GeneralRules": [
|
||||
{
|
||||
"Endpoint": "*",
|
||||
"Period": "1s",
|
||||
"Limit": 2000
|
||||
}
|
||||
]
|
||||
},
|
||||
"ClientRateLimitPolicies": {
|
||||
"ClientRules": [
|
||||
{
|
||||
"ClientId": "",
|
||||
"Rules": [
|
||||
{
|
||||
"Endpoint": "*",
|
||||
"Period": "1s",
|
||||
"Limit": 2000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.EntityFrameworkCore": "Information",
|
||||
"AspNetCoreRateLimit": "None",
|
||||
"System.Net.Http.HttpClient": "Warning"
|
||||
},
|
||||
"File": {
|
||||
"Enabled": true, // 启用文件日志
|
||||
"FileName": "logs/{0:yyyyMMdd}_{1}.log", // 日志文件
|
||||
"Append": true, // 追加覆盖
|
||||
"MinimumLevel": "Error", // 日志级别
|
||||
"FileSizeLimitBytes": 10485760, // 10M=10*1024*1024
|
||||
"MaxRollingFiles": 30 // 只保留30个文件
|
||||
},
|
||||
"Database": {
|
||||
"Enabled": true, // 启用数据库日志
|
||||
"MinimumLevel": "Information" // 日志级别
|
||||
},
|
||||
"Monitor": {
|
||||
"GlobalEnabled": true, // 启用全局拦截日志(建议生产环境关闭,否则对性能有影响)
|
||||
"IncludeOfMethods": [], // 拦截特定方法,当GlobalEnabled=false有效
|
||||
"ExcludeOfMethods": [], // 排除特定方法,当GlobalEnabled=true有效
|
||||
"BahLogLevel": "Information", // Oops.Oh 和 Oops.Bah 业务日志输出级别
|
||||
"WithReturnValue": true, // 是否包含返回值,默认true
|
||||
"ReturnValueThreshold": 0, // 返回值字符串阈值,默认0全量输出
|
||||
"JsonBehavior": "None", // 是否输出Json,默认None(OnlyJson、All)
|
||||
"JsonIndented": false, // 是否格式化Json
|
||||
"UseUtcTimestamp": false, // 时间格式UTC、LOCAL
|
||||
"ConsoleLog": true // 是否显示控制台日志
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"OAuth": {
|
||||
"Weixin": {
|
||||
"ClientId": "xxx",
|
||||
"ClientSecret": "xxx"
|
||||
},
|
||||
"Gitee": {
|
||||
"ClientId": "xxx",
|
||||
"ClientSecret": "xxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
// 短信配置
|
||||
"SMS": {
|
||||
"VerifyCodeExpireSeconds": 60, // 验证码缓存过期时间(秒),默认60秒
|
||||
// 阿里云短信
|
||||
"Aliyun": {
|
||||
"AccessKeyId": "",
|
||||
"AccessKeySecret": "",
|
||||
"Templates": [
|
||||
{
|
||||
"Id": "0",
|
||||
"SignName": "AdminNET 平台",
|
||||
"TemplateCode": "SMS_xxx",
|
||||
"Content": "您的验证码为:${code},请勿泄露于他人!"
|
||||
},
|
||||
{
|
||||
"Id": "1",
|
||||
"SignName": "AdminNET 平台",
|
||||
"TemplateCode": "SMS_xxx",
|
||||
"Content": "注册成功,感谢您的注册,请妥善保管您的账户信息"
|
||||
}
|
||||
]
|
||||
},
|
||||
// 腾讯云短信
|
||||
"Tencentyun": {
|
||||
"SdkAppId": "",
|
||||
"AccessKeyId": "",
|
||||
"AccessKeySecret": "",
|
||||
"Templates": [
|
||||
{
|
||||
"Id": "0",
|
||||
"SignName": "AdminNET 平台",
|
||||
"TemplateCode": "",
|
||||
"Content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
// 自定义短信接口
|
||||
"Custom": {
|
||||
"Enabled": false, // 是否启用自定义短信接口
|
||||
"Method": "GET", // 请求方法: GET 或 POST
|
||||
"ApiUrl": "https://api.xxxx.com/sms?u=xxxx&key=59e03f49c3dbb5033&m={mobile}&c={content}", // API接口地址,支持占位符: {mobile}, {content}, {code}
|
||||
"ContentType": "application/x-www-form-urlencoded", // POST请求的Content-Type: application/json 或 application/x-www-form-urlencoded
|
||||
"PostData": "", // POST请求的数据模板,支持占位符,JSON 格式示例: {"mobile":"{mobile}","content":"{content}","apikey":"your_key"};Form 格式示例: mobile={mobile}&content={content}&apikey=your_key
|
||||
"SuccessFlag": "0", // 成功响应标识,响应内容包含此字符串则认为发送成功
|
||||
"Templates": [
|
||||
{
|
||||
"Id": "0",
|
||||
"Content": "您的验证码为:{code},请勿泄露于他人!"
|
||||
},
|
||||
{
|
||||
"Id": "1",
|
||||
"Content": "注册成功,感谢您的注册,请妥善保管您的账户信息"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"SpecificationDocumentSettings": {
|
||||
"DocumentTitle": "常青树健康管理系统",
|
||||
"GroupOpenApiInfos": [
|
||||
{
|
||||
"Group": "Default",
|
||||
"Title": "常青树健康管理系统",
|
||||
"Description": "让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。",
|
||||
"Version": "1.0.0",
|
||||
"Order": 1000
|
||||
},
|
||||
{
|
||||
"Group": "All Groups",
|
||||
"Title": "所有接口",
|
||||
"Description": "让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。",
|
||||
"Version": "1.0.0",
|
||||
"Order": 0
|
||||
}
|
||||
],
|
||||
"DefaultGroupName": "Default", // 默认分组名
|
||||
"DocExpansionState": "List", // List、Full、None
|
||||
"EnableAllGroups": true,
|
||||
//"ServerDir": "xxx", // 二级虚拟目录并且开启 Servers 选择列表
|
||||
"HideServers": true,
|
||||
"Servers": [
|
||||
{
|
||||
"Url": "http://ip/xxx",
|
||||
"Description": "二级目录应用程序名"
|
||||
}
|
||||
],
|
||||
"LoginInfo": {
|
||||
"Enabled": true, // 是否开启Swagger登录
|
||||
"CheckUrl": "/api/swagger/checkUrl",
|
||||
"SubmitUrl": "/api/swagger/submitUrl",
|
||||
"EnableOnProduction": false // 是否在生产环境中自动开启
|
||||
},
|
||||
"EnumToNumber": true // 枚举类型生成值类型
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"Upload": {
|
||||
"Path": "upload/{yyyy}/{MM}/{dd}", // 文件上传目录
|
||||
"MaxSize": 51200, // 文件最大限制KB:1024*50
|
||||
"ContentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif", "image/bmp", "text/plain", "text/xml", "application/pdf", "application/msword", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "video/mp4", "application/wps-office.docx", "application/wps-office.xlsx", "application/wps-office.pptx", "application/vnd.android.package-archive" ],
|
||||
"EnableMd5": false // 启用文件MDF5验证-防止重复上传
|
||||
},
|
||||
"OSSProvider": {
|
||||
"Enabled": false,
|
||||
"Provider": "Minio", // OSS提供者 Invalid/Minio/Aliyun/QCloud/Qiniu/HuaweiCloud
|
||||
"Endpoint": "xxx.xxx.xxx.xxx:8090", // 节点/API地址(在腾讯云OSS中表示AppId)
|
||||
"Region": "xxx.xxx.xxx.xxx", // 地域
|
||||
"AccessKey": "",
|
||||
"SecretKey": "",
|
||||
"IsEnableHttps": false, // 是否启用HTTPS
|
||||
"IsEnableCache": true, // 是否启用缓存
|
||||
"Bucket": "admin.net",
|
||||
"CustomHost": "" // 自定义Host:拼接外链的Host,若空则使用Endpoint拼接
|
||||
},
|
||||
"SSHProvider": {
|
||||
"Enabled": false,
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 8222,
|
||||
"Username": "sshuser",
|
||||
"Password": "Password.1"
|
||||
},
|
||||
"MultiOSS": {
|
||||
"Enabled": true, // 是否启用多OSS功能
|
||||
"Description": "启用多OSS功能后,系统将支持多个存储提供者,可以通过BucketName或ProviderId指定使用哪个存储提供者"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"Wechat": {
|
||||
// 公众号
|
||||
"WechatAppId": "",
|
||||
"WechatAppSecret": "",
|
||||
"WechatToken": "", // 微信公众号服务器配置中的令牌(Token)
|
||||
"WechatEncodingAESKey": "", // 微信公众号服务器配置中的消息加解密密钥(EncodingAESKey)
|
||||
// 小程序
|
||||
"WxOpenAppId": "",
|
||||
"WxOpenAppSecret": "",
|
||||
"WxToken": "", // 小程序消息推送中的令牌(Token)
|
||||
"WxEncodingAESKey": "", // 小程序消息推送中的消息加解密密钥(EncodingAESKey)
|
||||
"QRImagePath": "" //小程序生成带参数二维码保存位置(绝对路径 eg: D:\\Web\\wwwroot\\upload\\QRImage),如果不配置,则默认保存在项目根目录下wwwroot\\upload\\QRImage
|
||||
},
|
||||
// 微信支付
|
||||
"WechatPay": {
|
||||
"AppId": "", // 微信公众平台AppId、开放平台AppId、小程序AppId、企业微信CorpId
|
||||
"MerchantId": "", // 商户平台的商户号
|
||||
"MerchantV3Secret": "", // 商户平台的APIv3密钥
|
||||
"MerchantCertificateSerialNumber": "", // 商户平台的证书序列号
|
||||
"MerchantCertificatePrivateKey": "WxPayCert/apiclient_key.pem" // 商户平台的API证书私钥(apiclient_key.pem文件内容)
|
||||
},
|
||||
// 支付回调
|
||||
"PayCallBack": {
|
||||
"WechatPayUrl": "https://xxx/api/sysWechatPay/payCallBack", // 微信支付回调
|
||||
"WechatRefundUrl": "", // 微信退款回调
|
||||
"AlipayUrl": "", // 支付宝支付回调
|
||||
"AlipayRefundUrl": "" // 支付宝退款回调
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Evergreen.Health.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 业务应用相关常量
|
||||
/// </summary>
|
||||
public class ApplicationConst
|
||||
{
|
||||
/// <summary>
|
||||
/// API分组名称
|
||||
/// </summary>
|
||||
public const string GroupName = "xxx业务应用";
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<NoWarn>1701;1702;1591;8632</NoWarn>
|
||||
<DocumentationFile></DocumentationFile>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<Nullable>disable</Nullable>
|
||||
<Copyright>Admin.NET</Copyright>
|
||||
<Description>常青树健康管理系统</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Configuration\**\*">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="wwwroot\**\*">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Admin.NET.Core\Admin.NET.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,15 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
global using Admin.NET.Core;
|
||||
global using Furion;
|
||||
global using Furion.DependencyInjection;
|
||||
global using Furion.DynamicApiController;
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using System;
|
||||
global using System.Threading.Tasks;
|
||||
@@ -0,0 +1,142 @@
|
||||
using SqlSugar;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Entities;
|
||||
|
||||
public enum HealthUserStatus { Disabled = 0, Enabled = 1 }
|
||||
|
||||
[SugarTable("health_user", "健康端用户")]
|
||||
[SugarIndex("idx_health_user_phone", nameof(Phone), OrderByType.Asc, IsUnique = true)]
|
||||
public sealed class HealthUser : EntityBaseDel
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "手机号", Length = 20)]
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "密码摘要", Length = 256)]
|
||||
[JsonIgnore, Newtonsoft.Json.JsonIgnore]
|
||||
public string PasswordHash { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "姓名或昵称", Length = 50)]
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "头像", Length = 500, IsNullable = true)]
|
||||
public string? AvatarUrl { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "状态")]
|
||||
public HealthUserStatus Status { get; set; } = HealthUserStatus.Enabled;
|
||||
|
||||
[SugarColumn(ColumnDescription = "最后登录时间", IsNullable = true)]
|
||||
public DateTime? LastLoginTime { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "最后登录IP", Length = 64, IsNullable = true)]
|
||||
public string? LastLoginIp { get; set; }
|
||||
}
|
||||
|
||||
[SugarTable("health_profile", "用户健康档案")]
|
||||
[SugarIndex("idx_health_profile_user", nameof(UserId), OrderByType.Asc, IsUnique = true)]
|
||||
public sealed class HealthProfile : EntityBase
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "健康用户Id")]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "出生日期", IsNullable = true)]
|
||||
public DateTime? Birthday { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "性别", Length = 20, IsNullable = true)]
|
||||
public string? Gender { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "身高厘米", DecimalDigits = 1, IsNullable = true)]
|
||||
public decimal? HeightCm { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "体重千克", DecimalDigits = 1, IsNullable = true)]
|
||||
public decimal? WeightKg { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "平均睡眠小时", DecimalDigits = 1, IsNullable = true)]
|
||||
public decimal? AverageSleepHours { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "每日运动分钟", IsNullable = true)]
|
||||
public int? DailyExerciseMinutes { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "饮食偏好", Length = 500, IsNullable = true)]
|
||||
public string? DietPreference { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "健康目标", Length = 500, IsNullable = true)]
|
||||
public string? HealthGoal { get; set; }
|
||||
}
|
||||
|
||||
[SugarTable("health_module_selection", "用户健康板块选择")]
|
||||
[SugarIndex("idx_health_module_user_code", nameof(UserId), OrderByType.Asc, nameof(ModuleCode), OrderByType.Asc, IsUnique = true)]
|
||||
public sealed class HealthModuleSelection : EntityBase
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "健康用户Id")]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "板块编码", Length = 32)]
|
||||
public string ModuleCode { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "是否启用")]
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
}
|
||||
|
||||
[SugarTable("health_plan", "个性化健康方案")]
|
||||
[SugarIndex("idx_health_plan_user", nameof(UserId), OrderByType.Asc)]
|
||||
public sealed class HealthPlan : EntityBase
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "健康用户Id")]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "方案版本")]
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[SugarColumn(ColumnDescription = "方案标题", Length = 100)]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "方案摘要", Length = 1000)]
|
||||
public string Summary { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "参数JSON", ColumnDataType = "text")]
|
||||
public string ParametersJson { get; set; } = "[]";
|
||||
|
||||
[SugarColumn(ColumnDescription = "当前方案")]
|
||||
public bool IsCurrent { get; set; } = true;
|
||||
}
|
||||
|
||||
[SugarTable("health_device_binding", "健康用户设备绑定")]
|
||||
[SugarIndex("idx_health_device_user_entity", nameof(UserId), OrderByType.Asc, nameof(EntityId), OrderByType.Asc, IsUnique = true)]
|
||||
public sealed class HealthDeviceBinding : EntityBase
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "健康用户Id")]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "Home Assistant实体Id", Length = 200)]
|
||||
public string EntityId { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "显示名称", Length = 100)]
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "设备类型", Length = 50, IsNullable = true)]
|
||||
public string? DeviceType { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "是否启用")]
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
}
|
||||
|
||||
[SugarTable("health_scene_execution", "健康场景执行记录")]
|
||||
[SugarIndex("idx_health_scene_user", nameof(UserId), OrderByType.Asc)]
|
||||
public sealed class HealthSceneExecution : EntityBase
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "健康用户Id")]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "场景编码", Length = 50)]
|
||||
public string SceneCode { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "场景名称", Length = 100)]
|
||||
public string SceneName { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "执行状态", Length = 32)]
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(ColumnDescription = "执行详情", ColumnDataType = "text", IsNullable = true)]
|
||||
public string? Detail { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Models;
|
||||
|
||||
public sealed class HealthRegisterInput
|
||||
{
|
||||
[Required, RegularExpression(@"^1[3-9]\d{9}$", ErrorMessage = "请输入正确的手机号码")]
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
|
||||
[Required, MinLength(8), MaxLength(32)]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
[Required, MaxLength(50)]
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class HealthLoginInput
|
||||
{
|
||||
[Required]
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class HealthAuthOutput
|
||||
{
|
||||
public string AccessToken { get; set; } = string.Empty;
|
||||
public string RefreshToken { get; set; } = string.Empty;
|
||||
public HealthUserOutput User { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class HealthUserOutput
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string? AvatarUrl { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SaveHealthProfileInput
|
||||
{
|
||||
public DateTime? Birthday { get; set; }
|
||||
|
||||
[MaxLength(20)]
|
||||
public string? Gender { get; set; }
|
||||
|
||||
[Range(80, 250)]
|
||||
public decimal? HeightCm { get; set; }
|
||||
|
||||
[Range(20, 300)]
|
||||
public decimal? WeightKg { get; set; }
|
||||
|
||||
[Range(0, 24)]
|
||||
public decimal? AverageSleepHours { get; set; }
|
||||
|
||||
[Range(0, 1440)]
|
||||
public int? DailyExerciseMinutes { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? DietPreference { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? HealthGoal { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SaveHealthModulesInput
|
||||
{
|
||||
[Required, MinLength(1)]
|
||||
public List<string> ModuleCodes { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class HealthPlanItemOutput
|
||||
{
|
||||
public string ModuleCode { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Target { get; set; } = string.Empty;
|
||||
public string Advice { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class HealthPlanOutput
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public int Version { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Summary { get; set; } = string.Empty;
|
||||
public DateTime CreateTime { get; set; }
|
||||
public List<HealthPlanItemOutput> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class HealthDashboardOutput
|
||||
{
|
||||
public HealthUserOutput User { get; set; } = new();
|
||||
public SaveHealthProfileInput? Profile { get; set; }
|
||||
public List<string> SelectedModules { get; set; } = [];
|
||||
public HealthPlanOutput? CurrentPlan { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Security;
|
||||
|
||||
internal static class HealthPasswordHasher
|
||||
{
|
||||
private const int Iterations = 120_000;
|
||||
private const int SaltSize = 16;
|
||||
private const int KeySize = 32;
|
||||
private const string Algorithm = "pbkdf2-sha256";
|
||||
|
||||
public static string Hash(string password)
|
||||
{
|
||||
var salt = RandomNumberGenerator.GetBytes(SaltSize);
|
||||
var key = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, HashAlgorithmName.SHA256, KeySize);
|
||||
return "$" + Algorithm + "$" + Iterations + "$" + Convert.ToBase64String(salt) + "$" + Convert.ToBase64String(key);
|
||||
}
|
||||
|
||||
public static bool Verify(string password, string encodedHash)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parts = encodedHash.Split('$', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 4 || parts[0] != Algorithm || !int.TryParse(parts[1], out var iterations))
|
||||
return false;
|
||||
|
||||
var salt = Convert.FromBase64String(parts[2]);
|
||||
var expected = Convert.FromBase64String(parts[3]);
|
||||
var actual = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, expected.Length);
|
||||
return CryptographicOperations.FixedTimeEquals(actual, expected);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
using Evergreen.Health.Application.Health.Entities;
|
||||
using Evergreen.Health.Application.Health.Models;
|
||||
using Evergreen.Health.Application.Health.Security;
|
||||
using Furion.DataEncryption;
|
||||
using Furion.DatabaseAccessor;
|
||||
using Furion.FriendlyException;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using SqlSugar;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Services;
|
||||
|
||||
[ApiDescriptionSettings("健康管理", Name = "Health", Order = 50)]
|
||||
public sealed class HealthAppService : IDynamicApiController, ITransient
|
||||
{
|
||||
private const string HealthUserIdClaim = "health_user_id";
|
||||
private static readonly HashSet<string> SupportedModules = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"sleep", "exercise", "diet"
|
||||
};
|
||||
|
||||
private readonly SqlSugarRepository<HealthUser> _userRepository;
|
||||
private readonly SqlSugarRepository<HealthProfile> _profileRepository;
|
||||
private readonly SqlSugarRepository<HealthModuleSelection> _moduleRepository;
|
||||
private readonly SqlSugarRepository<HealthPlan> _planRepository;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public HealthAppService(
|
||||
SqlSugarRepository<HealthUser> userRepository,
|
||||
SqlSugarRepository<HealthProfile> profileRepository,
|
||||
SqlSugarRepository<HealthModuleSelection> moduleRepository,
|
||||
SqlSugarRepository<HealthPlan> planRepository,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_profileRepository = profileRepository;
|
||||
_moduleRepository = moduleRepository;
|
||||
_planRepository = planRepository;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/api/health/auth/register")]
|
||||
[UnitOfWork]
|
||||
public async Task<HealthAuthOutput> Register(HealthRegisterInput input)
|
||||
{
|
||||
var phone = input.Phone.Trim();
|
||||
if (await _userRepository.IsAnyAsync(user => user.Phone == phone && !user.IsDelete))
|
||||
throw Oops.Oh("该手机号已经注册");
|
||||
|
||||
var user = await _userRepository.AsInsertable(new HealthUser
|
||||
{
|
||||
Phone = phone,
|
||||
DisplayName = input.DisplayName.Trim(),
|
||||
PasswordHash = HealthPasswordHasher.Hash(input.Password),
|
||||
Status = HealthUserStatus.Enabled
|
||||
}).ExecuteReturnEntityAsync();
|
||||
|
||||
await _profileRepository.InsertAsync(new HealthProfile { UserId = user.Id });
|
||||
await _moduleRepository.InsertRangeAsync(new List<HealthModuleSelection>
|
||||
{
|
||||
new HealthModuleSelection { UserId = user.Id, ModuleCode = "sleep", IsEnabled = true },
|
||||
new HealthModuleSelection { UserId = user.Id, ModuleCode = "exercise", IsEnabled = true },
|
||||
new HealthModuleSelection { UserId = user.Id, ModuleCode = "diet", IsEnabled = true }
|
||||
});
|
||||
|
||||
return CreateAuthOutput(user);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/api/health/auth/login")]
|
||||
public async Task<HealthAuthOutput> Login(HealthLoginInput input)
|
||||
{
|
||||
var phone = input.Phone.Trim();
|
||||
var user = await _userRepository.GetFirstAsync(item => item.Phone == phone && !item.IsDelete);
|
||||
if (user is null || !HealthPasswordHasher.Verify(input.Password, user.PasswordHash))
|
||||
throw Oops.Oh("手机号或密码错误").StatusCode(401);
|
||||
if (user.Status != HealthUserStatus.Enabled)
|
||||
throw Oops.Oh("账号已停用").StatusCode(403);
|
||||
|
||||
user.LastLoginTime = DateTime.Now;
|
||||
user.LastLoginIp = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString();
|
||||
await _userRepository.AsUpdateable(user)
|
||||
.UpdateColumns(item => new { item.LastLoginTime, item.LastLoginIp })
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
return CreateAuthOutput(user);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("/api/health/dashboard")]
|
||||
public async Task<HealthDashboardOutput> GetDashboard()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
var user = await GetCurrentUser(userId);
|
||||
var profile = await _profileRepository.GetFirstAsync(item => item.UserId == userId);
|
||||
var modules = await _moduleRepository.AsQueryable()
|
||||
.Where(item => item.UserId == userId && item.IsEnabled)
|
||||
.Select(item => item.ModuleCode)
|
||||
.ToListAsync();
|
||||
var plan = await _planRepository.AsQueryable()
|
||||
.Where(item => item.UserId == userId && item.IsCurrent)
|
||||
.OrderByDescending(item => item.Version)
|
||||
.FirstAsync();
|
||||
|
||||
return new HealthDashboardOutput
|
||||
{
|
||||
User = MapUser(user),
|
||||
Profile = profile is null ? null : MapProfile(profile),
|
||||
SelectedModules = modules,
|
||||
CurrentPlan = plan is null ? null : MapPlan(plan)
|
||||
};
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPut("/api/health/profile")]
|
||||
public async Task<SaveHealthProfileInput> SaveProfile(SaveHealthProfileInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
var profile = await _profileRepository.GetFirstAsync(item => item.UserId == userId);
|
||||
if (profile is null)
|
||||
{
|
||||
profile = new HealthProfile { UserId = userId };
|
||||
ApplyProfile(profile, input);
|
||||
await _profileRepository.InsertAsync(profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyProfile(profile, input);
|
||||
await _profileRepository.UpdateAsync(profile);
|
||||
}
|
||||
|
||||
return MapProfile(profile);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPut("/api/health/modules")]
|
||||
[UnitOfWork]
|
||||
public async Task<List<string>> SaveModules(SaveHealthModulesInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
var modules = input.ModuleCodes
|
||||
.Select(item => item.Trim().ToLowerInvariant())
|
||||
.Where(SupportedModules.Contains)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (modules.Count == 0)
|
||||
throw Oops.Oh("请至少选择一个健康板块");
|
||||
|
||||
await _moduleRepository.DeleteAsync(item => item.UserId == userId);
|
||||
await _moduleRepository.InsertRangeAsync(modules.Select(module => new HealthModuleSelection
|
||||
{
|
||||
UserId = userId,
|
||||
ModuleCode = module,
|
||||
IsEnabled = true
|
||||
}).ToList());
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("/api/health/plans/generate")]
|
||||
[UnitOfWork]
|
||||
public async Task<HealthPlanOutput> GeneratePlan()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
var profile = await _profileRepository.GetFirstAsync(item => item.UserId == userId);
|
||||
var modules = await _moduleRepository.AsQueryable()
|
||||
.Where(item => item.UserId == userId && item.IsEnabled)
|
||||
.Select(item => item.ModuleCode)
|
||||
.ToListAsync();
|
||||
|
||||
if (modules.Count == 0)
|
||||
throw Oops.Oh("请先选择健康板块");
|
||||
|
||||
var items = BuildPlanItems(profile, modules);
|
||||
var currentVersion = await _planRepository.AsQueryable()
|
||||
.Where(item => item.UserId == userId)
|
||||
.MaxAsync(item => (int?)item.Version) ?? 0;
|
||||
|
||||
await _planRepository.Context.Updateable<HealthPlan>()
|
||||
.SetColumns(item => item.IsCurrent == false)
|
||||
.Where(item => item.UserId == userId && item.IsCurrent)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
var plan = await _planRepository.AsInsertable(new HealthPlan
|
||||
{
|
||||
UserId = userId,
|
||||
Version = currentVersion + 1,
|
||||
Title = "您的专属健康方案",
|
||||
Summary = BuildSummary(profile, modules),
|
||||
ParametersJson = JsonSerializer.Serialize(items),
|
||||
IsCurrent = true
|
||||
}).ExecuteReturnEntityAsync();
|
||||
|
||||
return MapPlan(plan);
|
||||
}
|
||||
|
||||
private long GetCurrentUserId()
|
||||
{
|
||||
var value = _httpContextAccessor.HttpContext?.User.FindFirstValue(HealthUserIdClaim);
|
||||
if (!long.TryParse(value, out var userId))
|
||||
throw Oops.Oh("登录状态已失效").StatusCode(401);
|
||||
return userId;
|
||||
}
|
||||
|
||||
private async Task<HealthUser> GetCurrentUser(long userId)
|
||||
{
|
||||
return await _userRepository.GetFirstAsync(item => item.Id == userId && !item.IsDelete)
|
||||
?? throw Oops.Oh("用户不存在").StatusCode(401);
|
||||
}
|
||||
|
||||
private static void ApplyProfile(HealthProfile profile, SaveHealthProfileInput input)
|
||||
{
|
||||
profile.Birthday = input.Birthday;
|
||||
profile.Gender = input.Gender?.Trim();
|
||||
profile.HeightCm = input.HeightCm;
|
||||
profile.WeightKg = input.WeightKg;
|
||||
profile.AverageSleepHours = input.AverageSleepHours;
|
||||
profile.DailyExerciseMinutes = input.DailyExerciseMinutes;
|
||||
profile.DietPreference = input.DietPreference?.Trim();
|
||||
profile.HealthGoal = input.HealthGoal?.Trim();
|
||||
}
|
||||
|
||||
private static SaveHealthProfileInput MapProfile(HealthProfile profile) => new()
|
||||
{
|
||||
Birthday = profile.Birthday,
|
||||
Gender = profile.Gender,
|
||||
HeightCm = profile.HeightCm,
|
||||
WeightKg = profile.WeightKg,
|
||||
AverageSleepHours = profile.AverageSleepHours,
|
||||
DailyExerciseMinutes = profile.DailyExerciseMinutes,
|
||||
DietPreference = profile.DietPreference,
|
||||
HealthGoal = profile.HealthGoal
|
||||
};
|
||||
|
||||
private static HealthUserOutput MapUser(HealthUser user) => new()
|
||||
{
|
||||
Id = user.Id,
|
||||
Phone = user.Phone,
|
||||
DisplayName = user.DisplayName,
|
||||
AvatarUrl = user.AvatarUrl
|
||||
};
|
||||
|
||||
private static List<HealthPlanItemOutput> BuildPlanItems(HealthProfile? profile, IEnumerable<string> modules)
|
||||
{
|
||||
var items = new List<HealthPlanItemOutput>();
|
||||
foreach (var module in modules)
|
||||
{
|
||||
switch (module)
|
||||
{
|
||||
case "sleep":
|
||||
items.Add(new HealthPlanItemOutput
|
||||
{
|
||||
ModuleCode = module,
|
||||
Title = "睡眠质量",
|
||||
Target = profile?.AverageSleepHours is < 7 ? "逐步达到每晚 7–8 小时" : "保持每晚 7–8 小时",
|
||||
Advice = "固定入睡时间,睡前一小时降低灯光亮度,并使用卧室舒睡场景。"
|
||||
});
|
||||
break;
|
||||
case "exercise":
|
||||
items.Add(new HealthPlanItemOutput
|
||||
{
|
||||
ModuleCode = module,
|
||||
Title = "运动情况",
|
||||
Target = profile?.DailyExerciseMinutes is >= 30 ? "保持每日 30 分钟以上活动" : "从每日 20 分钟步行开始",
|
||||
Advice = "优先安排低到中等强度运动,每坐一小时起身活动 3–5 分钟。"
|
||||
});
|
||||
break;
|
||||
case "diet":
|
||||
items.Add(new HealthPlanItemOutput
|
||||
{
|
||||
ModuleCode = module,
|
||||
Title = "饮食情况",
|
||||
Target = "三餐规律,晚餐不过量",
|
||||
Advice = string.IsNullOrWhiteSpace(profile?.DietPreference)
|
||||
? "记录三餐与饮水情况,优先增加蔬菜和优质蛋白。"
|
||||
: $"结合“{profile.DietPreference}”偏好控制总量,并保持食物多样性。"
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private static string BuildSummary(HealthProfile? profile, IReadOnlyCollection<string> modules)
|
||||
{
|
||||
var bmiText = string.Empty;
|
||||
if (profile?.HeightCm > 0 && profile.WeightKg > 0)
|
||||
{
|
||||
var height = profile.HeightCm.Value / 100m;
|
||||
var bmi = Math.Round(profile.WeightKg.Value / (height * height), 1);
|
||||
bmiText = $" 当前 BMI 约为 {bmi}。";
|
||||
}
|
||||
|
||||
return $"方案依据您选择的 {modules.Count} 个健康板块生成。{bmiText}第一版参数用于健康管理演示,不替代专业医疗诊断。";
|
||||
}
|
||||
|
||||
private static HealthPlanOutput MapPlan(HealthPlan plan) => new()
|
||||
{
|
||||
Id = plan.Id,
|
||||
Version = plan.Version,
|
||||
Title = plan.Title,
|
||||
Summary = plan.Summary,
|
||||
CreateTime = plan.CreateTime,
|
||||
Items = JsonSerializer.Deserialize<List<HealthPlanItemOutput>>(plan.ParametersJson) ?? []
|
||||
};
|
||||
|
||||
private static HealthAuthOutput CreateAuthOutput(HealthUser user)
|
||||
{
|
||||
const int accessTokenMinutes = 24 * 60;
|
||||
const int refreshTokenMinutes = 7 * 24 * 60;
|
||||
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
|
||||
{
|
||||
[HealthUserIdClaim] = user.Id,
|
||||
[ClaimTypes.NameIdentifier] = user.Id,
|
||||
[ClaimTypes.Name] = user.DisplayName,
|
||||
[ClaimTypes.MobilePhone] = user.Phone,
|
||||
["account_type"] = "health_user"
|
||||
}, accessTokenMinutes);
|
||||
|
||||
return new HealthAuthOutput
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
RefreshToken = JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenMinutes),
|
||||
User = MapUser(user)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Evergreen.Health.Application;
|
||||
|
||||
[AppStartup(100)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user