SysDatabaseService.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. // 麻省理工学院许可证
  2. //
  3. // 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司 联系电话/微信:18020030720 QQ:515096995
  4. //
  5. // 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
  6. //
  7. // 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
  8. // 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
  9. using Magicodes.ExporterAndImporter.Core.Extension;
  10. namespace Admin.NET.Core.Service;
  11. /// <summary>
  12. /// 系统数据库管理服务
  13. /// </summary>
  14. [ApiDescriptionSettings(Order = 250)]
  15. public class SysDatabaseService : IDynamicApiController, ITransient
  16. {
  17. /// <summary>
  18. /// 保存标注了 JsonIgnore 的Property的值信息
  19. /// </summary>
  20. public class JsonIgnoredPropertyData
  21. {
  22. /// <summary>
  23. /// 对应的记录索引
  24. /// </summary>
  25. public int RecordIndex { get; set; }
  26. /// <summary>
  27. /// 属性名
  28. /// </summary>
  29. public string Name { get; set; }
  30. /// <summary>
  31. /// 属性值的字符串描述
  32. /// </summary>
  33. public string Value { get; set; }
  34. }
  35. private readonly ISqlSugarClient _db;
  36. private readonly IViewEngine _viewEngine;
  37. private readonly CodeGenOptions _codeGenOptions;
  38. public SysDatabaseService(ISqlSugarClient db,
  39. IViewEngine viewEngine,
  40. IOptions<CodeGenOptions> codeGenOptions)
  41. {
  42. _db = db;
  43. _viewEngine = viewEngine;
  44. _codeGenOptions = codeGenOptions.Value;
  45. }
  46. /// <summary>
  47. /// 获取库列表
  48. /// </summary>
  49. /// <returns></returns>
  50. [DisplayName("获取库列表")]
  51. public List<dynamic> GetList()
  52. {
  53. return App.GetOptions<DbConnectionOptions>().ConnectionConfigs.Select(u => u.ConfigId).ToList();
  54. }
  55. /// <summary>
  56. /// 获取字段列表
  57. /// </summary>
  58. /// <param name="tableName">表名</param>
  59. /// <param name="configId">ConfigId</param>
  60. /// <returns></returns>
  61. [AllowAnonymous]
  62. [DisplayName("获取字段列表")]
  63. public List<DbColumnOutput> GetColumnList(string tableName, string configId = SqlSugarConst.MainConfigId)
  64. {
  65. var db = _db.AsTenant().GetConnectionScope(configId);
  66. if (string.IsNullOrWhiteSpace(tableName))
  67. return new List<DbColumnOutput>();
  68. return db.DbMaintenance.GetColumnInfosByTableName(tableName, false).Adapt<List<DbColumnOutput>>();
  69. }
  70. /// <summary>
  71. /// 增加列
  72. /// </summary>
  73. /// <param name="input"></param>
  74. [ApiDescriptionSettings(Name = "AddColumn"), HttpPost]
  75. [DisplayName("增加列")]
  76. public void AddColumn(DbColumnInput input)
  77. {
  78. var column = new DbColumnInfo
  79. {
  80. ColumnDescription = input.ColumnDescription,
  81. DbColumnName = input.DbColumnName,
  82. IsIdentity = input.IsIdentity == 1,
  83. IsNullable = input.IsNullable == 1,
  84. IsPrimarykey = input.IsPrimarykey == 1,
  85. Length = input.Length,
  86. DecimalDigits = input.DecimalDigits,
  87. DataType = input.DataType
  88. };
  89. var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
  90. db.DbMaintenance.AddColumn(input.TableName, column);
  91. db.DbMaintenance.AddColumnRemark(input.DbColumnName, input.TableName, input.ColumnDescription);
  92. if (column.IsPrimarykey)
  93. db.DbMaintenance.AddPrimaryKey(input.TableName, input.DbColumnName);
  94. }
  95. /// <summary>
  96. /// 删除列
  97. /// </summary>
  98. /// <param name="input"></param>
  99. [ApiDescriptionSettings(Name = "DeleteColumn"), HttpPost]
  100. [DisplayName("删除列")]
  101. public void DeleteColumn(DeleteDbColumnInput input)
  102. {
  103. var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
  104. db.DbMaintenance.DropColumn(input.TableName, input.DbColumnName);
  105. }
  106. /// <summary>
  107. /// 编辑列
  108. /// </summary>
  109. /// <param name="input"></param>
  110. [ApiDescriptionSettings(Name = "UpdateColumn"), HttpPost]
  111. [DisplayName("编辑列")]
  112. public void UpdateColumn(UpdateDbColumnInput input)
  113. {
  114. var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
  115. db.DbMaintenance.RenameColumn(input.TableName, input.OldColumnName, input.ColumnName);
  116. if (db.DbMaintenance.IsAnyColumnRemark(input.ColumnName, input.TableName))
  117. db.DbMaintenance.DeleteColumnRemark(input.ColumnName, input.TableName);
  118. db.DbMaintenance.AddColumnRemark(input.ColumnName, input.TableName, string.IsNullOrWhiteSpace(input.Description) ? input.ColumnName : input.Description);
  119. }
  120. /// <summary>
  121. /// 获取表列表
  122. /// </summary>
  123. /// <param name="configId">ConfigId</param>
  124. /// <returns></returns>
  125. [DisplayName("获取表列表")]
  126. public List<DbTableInfo> GetTableList(string configId = SqlSugarConst.MainConfigId)
  127. {
  128. var db = _db.AsTenant().GetConnectionScope(configId);
  129. return db.DbMaintenance.GetTableInfoList(false);
  130. }
  131. /// <summary>
  132. /// 增加表
  133. /// </summary>
  134. /// <param name="input"></param>
  135. [ApiDescriptionSettings(Name = "AddTable"), HttpPost]
  136. [DisplayName("增加表")]
  137. public void AddTable(DbTableInput input)
  138. {
  139. if (input.DbColumnInfoList == null || !input.DbColumnInfoList.Any())
  140. throw Oops.Oh(ErrorCodeEnum.db1000);
  141. if (input.DbColumnInfoList.GroupBy(q => q.DbColumnName).Any(q => q.Count() > 1))
  142. throw Oops.Oh(ErrorCodeEnum.db1002);
  143. var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => u.ConfigId == input.ConfigId);
  144. var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
  145. var typeBilder = db.DynamicBuilder().CreateClass(input.TableName, new SugarTable() { TableName = input.TableName, TableDescription = input.Description });
  146. input.DbColumnInfoList.ForEach(m =>
  147. {
  148. var dbColumnName = config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(m.DbColumnName.Trim()) : m.DbColumnName.Trim();
  149. // 虚拟类都默认string类型,具体以列数据类型为准
  150. typeBilder.CreateProperty(dbColumnName, typeof(string), new SugarColumn()
  151. {
  152. IsPrimaryKey = m.IsPrimarykey == 1,
  153. IsIdentity = m.IsIdentity == 1,
  154. ColumnDataType = m.DataType,
  155. Length = m.Length,
  156. IsNullable = m.IsNullable == 1,
  157. DecimalDigits = m.DecimalDigits,
  158. ColumnDescription = m.ColumnDescription,
  159. });
  160. });
  161. db.CodeFirst.InitTables(typeBilder.BuilderType());
  162. }
  163. /// <summary>
  164. /// 删除表
  165. /// </summary>
  166. /// <param name="input"></param>
  167. [ApiDescriptionSettings(Name = "DeleteTable"), HttpPost]
  168. [DisplayName("删除表")]
  169. public void DeleteTable(DeleteDbTableInput input)
  170. {
  171. var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
  172. db.DbMaintenance.DropTable(input.TableName);
  173. }
  174. /// <summary>
  175. /// 编辑表
  176. /// </summary>
  177. /// <param name="input"></param>
  178. [ApiDescriptionSettings(Name = "UpdateTable"), HttpPost]
  179. [DisplayName("编辑表")]
  180. public void UpdateTable(UpdateDbTableInput input)
  181. {
  182. var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
  183. db.DbMaintenance.RenameTable(input.OldTableName, input.TableName);
  184. try
  185. {
  186. if (db.DbMaintenance.IsAnyTableRemark(input.TableName))
  187. db.DbMaintenance.DeleteTableRemark(input.TableName);
  188. }
  189. catch (NotSupportedException)
  190. {
  191. //Ignore 不支持该方法则不处理
  192. }
  193. db.DbMaintenance.AddTableRemark(input.TableName, input.Description);
  194. }
  195. /// <summary>
  196. /// 创建实体
  197. /// </summary>
  198. /// <param name="input"></param>
  199. [ApiDescriptionSettings(Name = "CreateEntity"), HttpPost]
  200. [DisplayName("创建实体")]
  201. public void CreateEntity(CreateEntityInput input)
  202. {
  203. var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => u.ConfigId == input.ConfigId);
  204. input.Position = string.IsNullOrWhiteSpace(input.Position) ? "Admin.NET.Application" : input.Position;
  205. input.EntityName = string.IsNullOrWhiteSpace(input.EntityName) ? (config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(input.TableName, null) : input.TableName) : input.EntityName;
  206. string[] dbColumnNames = new string[0];
  207. // Entity.cs.vm中是允许创建没有基类的实体的,所以这里也要做出相同的判断
  208. if (!string.IsNullOrWhiteSpace(input.BaseClassName))
  209. {
  210. _codeGenOptions.EntityBaseColumn.TryGetValue(input.BaseClassName, out dbColumnNames);
  211. if (dbColumnNames is null || dbColumnNames is { Length: 0 })
  212. throw Oops.Oh("基类配置文件不存在此类型");
  213. }
  214. var templatePath = GetEntityTemplatePath();
  215. var targetPath = GetEntityTargetPath(input);
  216. var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
  217. DbTableInfo dbTableInfo = db.DbMaintenance.GetTableInfoList(false).FirstOrDefault(m => m.Name == input.TableName || m.Name == input.TableName.ToLower()) ?? throw Oops.Oh(ErrorCodeEnum.db1001);
  218. List<DbColumnInfo> dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false);
  219. dbColumnInfos.ForEach(u =>
  220. {
  221. u.PropertyName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, dbColumnNames) : u.DbColumnName; // 转下划线后的列名需要再转回来
  222. u.DataType = CodeGenUtil.ConvertDataType(u, config.DbType);
  223. });
  224. if (_codeGenOptions.BaseEntityNames.Contains(input.BaseClassName, StringComparer.OrdinalIgnoreCase))
  225. dbColumnInfos = dbColumnInfos.Where(c => !dbColumnNames.Contains(c.DbColumnName, StringComparer.OrdinalIgnoreCase)).ToList();
  226. var tContent = File.ReadAllText(templatePath);
  227. var tResult = _viewEngine.RunCompileFromCached(tContent, new
  228. {
  229. NameSpace = $"{input.Position}.Entity",
  230. input.TableName,
  231. input.EntityName,
  232. BaseClassName = string.IsNullOrWhiteSpace(input.BaseClassName) ? "" : $" : {input.BaseClassName}",
  233. input.ConfigId,
  234. dbTableInfo.Description,
  235. TableField = dbColumnInfos
  236. });
  237. File.WriteAllText(targetPath, tResult, Encoding.UTF8);
  238. }
  239. /// <summary>
  240. /// 创建 SeedData
  241. /// </summary>
  242. /// <param name="input"></param>
  243. [ApiDescriptionSettings(Name = "CreateSeedData"), HttpPost]
  244. [DisplayName("创建 SeedData")]
  245. public async void CreateSeedData(CreateSeedDataInput input)
  246. {
  247. var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => u.ConfigId == input.ConfigId);
  248. input.Position = string.IsNullOrWhiteSpace(input.Position) ? "Admin.NET.Core" : input.Position;
  249. var templatePath = GetSeedDataTemplatePath();
  250. var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
  251. var tableInfo = db.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == input.TableName);//表名
  252. List<DbColumnInfo> dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false); //所有字段
  253. IEnumerable<EntityInfo> entityInfos = await GetEntityInfos();
  254. Type enityType = null;
  255. foreach (var item in entityInfos)
  256. {
  257. if (tableInfo.Name.ToLower() != (config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(item.DbTableName) : item.DbTableName).ToLower()) continue;
  258. enityType = item.Type;
  259. break;
  260. }
  261. input.EntityName = enityType.Name;
  262. input.SeedDataName = enityType.Name + "SeedData";
  263. if (!string.IsNullOrWhiteSpace(input.Suffix))
  264. input.SeedDataName += input.Suffix;
  265. var targetPath = GetSeedDataTargetPath(input);
  266. // 查询出所有数据
  267. var query = db.QueryableByObject(enityType);
  268. // 查询有没有合适的排序字段,有的话用来排序,保证SeedData的插入顺序合适
  269. DbColumnInfo orderField = null;
  270. // 优先用创建时间排序
  271. orderField = dbColumnInfos.Where(u => u.DbColumnName.ToLower() == "create_time" || u.DbColumnName.ToLower() == "createtime").FirstOrDefault();
  272. if (orderField != null)
  273. {
  274. query.OrderBy(orderField.DbColumnName);
  275. }
  276. // 其次用ID排充
  277. orderField = dbColumnInfos.Where(u => u.DbColumnName.ToLower() == "id").FirstOrDefault();
  278. if (orderField != null)
  279. {
  280. query.OrderBy(orderField.DbColumnName);
  281. }
  282. object records = query.ToList();
  283. string recordsJSON = Newtonsoft.Json.JsonConvert.SerializeObject(records, Formatting.Indented);
  284. //检查有没有 System.Text.Json.Serialization.JsonIgnore 的属性
  285. var jsonIgnoreProperties = enityType.GetProperties().Where(p =>
  286. p.GetAttribute<System.Text.Json.Serialization.JsonIgnoreAttribute>() != null ||
  287. p.GetAttribute<Newtonsoft.Json.JsonIgnoreAttribute>() != null
  288. ).ToList();
  289. List<List<JsonIgnoredPropertyData>> jsonIgnoreInfo = new List<List<JsonIgnoredPropertyData>>();
  290. if (jsonIgnoreProperties.Count > 0) {
  291. int recordIndex = 0;
  292. foreach (var r in (IEnumerable)records)
  293. {
  294. List<JsonIgnoredPropertyData> record = new List<JsonIgnoredPropertyData>();
  295. foreach (var item in jsonIgnoreProperties)
  296. {
  297. object v = item.GetValue(r);
  298. string strValue = "null";
  299. if (v != null)
  300. {
  301. strValue = v.ToString();
  302. if (v.GetType() == typeof(string))
  303. strValue = "\"" + strValue + "\"";
  304. else if (v.GetType() == typeof(DateTime))
  305. strValue = "DateTime.Parse(\"" + ((DateTime)v).ToString("yyyy/MM/dd HH:mm:ss") + "\")"; //这个日期方式不知道对不对
  306. }
  307. record.Add(new JsonIgnoredPropertyData { RecordIndex = recordIndex, Name = item.Name, Value = strValue });
  308. }
  309. recordIndex++;
  310. jsonIgnoreInfo.Add(record);
  311. }
  312. }
  313. // 目前为止 jsonIgnoreInfo 中保存了 JsonIgnore 的属性和值的对应关系
  314. var tContent = File.ReadAllText(templatePath);
  315. var data = new
  316. {
  317. NameSpace = $"{input.Position}.SeedData",
  318. EntityNameSpace = enityType.Namespace,
  319. input.TableName,
  320. input.EntityName,
  321. input.SeedDataName,
  322. input.ConfigId,
  323. tableInfo.Description,
  324. JsonIgnoreInfo = jsonIgnoreInfo,
  325. RecordsJSON = recordsJSON
  326. };
  327. var tResult = _viewEngine.RunCompile(tContent, data, builderAction: builder =>
  328. {
  329. builder.AddAssemblyReferenceByName("System.Linq");
  330. builder.AddAssemblyReferenceByName("System.Collections");
  331. builder.AddUsing("System.Collections.Generic");
  332. builder.AddUsing("System.Linq");
  333. });
  334. File.WriteAllText(targetPath, tResult, Encoding.UTF8);
  335. }
  336. /// <summary>
  337. /// 获取库表信息
  338. /// </summary>
  339. /// <returns></returns>
  340. private async Task<IEnumerable<EntityInfo>> GetEntityInfos()
  341. {
  342. var entityInfos = new List<EntityInfo>();
  343. var type = typeof(SugarTable);
  344. var types = new List<Type>();
  345. if (_codeGenOptions.EntityAssemblyNames != null)
  346. {
  347. foreach (var assemblyName in _codeGenOptions.EntityAssemblyNames)
  348. {
  349. Assembly asm = Assembly.Load(assemblyName);
  350. types.AddRange(asm.GetExportedTypes().ToList());
  351. }
  352. }
  353. bool IsMyAttribute(Attribute[] o)
  354. {
  355. foreach (Attribute a in o)
  356. {
  357. if (a.GetType() == type)
  358. return true;
  359. }
  360. return false;
  361. }
  362. Type[] cosType = types.Where(o =>
  363. {
  364. return IsMyAttribute(Attribute.GetCustomAttributes(o, true));
  365. }
  366. ).ToArray();
  367. foreach (var c in cosType)
  368. {
  369. var sugarAttribute = c.GetCustomAttributes(type, true)?.FirstOrDefault();
  370. var des = c.GetCustomAttributes(typeof(DescriptionAttribute), true);
  371. var description = "";
  372. if (des.Length > 0)
  373. {
  374. description = ((DescriptionAttribute)des[0]).Description;
  375. }
  376. entityInfos.Add(new EntityInfo()
  377. {
  378. EntityName = c.Name,
  379. DbTableName = sugarAttribute == null ? c.Name : ((SugarTable)sugarAttribute).TableName,
  380. TableDescription = description,
  381. Type = c
  382. });
  383. }
  384. return await Task.FromResult(entityInfos);
  385. }
  386. /// <summary>
  387. /// 获取实体模板文件路径
  388. /// </summary>
  389. /// <returns></returns>
  390. private static string GetEntityTemplatePath()
  391. {
  392. var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template");
  393. return Path.Combine(templatePath, "Entity.cs.vm");
  394. }
  395. /// <summary>
  396. /// 获取SeedData模板文件路径
  397. /// </summary>
  398. /// <returns></returns>
  399. private static string GetSeedDataTemplatePath()
  400. {
  401. var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template");
  402. return Path.Combine(templatePath, "SeedData.cs.vm");
  403. }
  404. /// <summary>
  405. /// 设置生成实体文件路径
  406. /// </summary>
  407. /// <param name="input"></param>
  408. /// <returns></returns>
  409. private static string GetEntityTargetPath(CreateEntityInput input)
  410. {
  411. var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "Entity");
  412. if (!Directory.Exists(backendPath))
  413. Directory.CreateDirectory(backendPath);
  414. return Path.Combine(backendPath, input.EntityName + ".cs");
  415. }
  416. /// <summary>
  417. /// 设置生成SeedData文件路径
  418. /// </summary>
  419. /// <param name="input"></param>
  420. /// <returns></returns>
  421. private static string GetSeedDataTargetPath(CreateSeedDataInput input)
  422. {
  423. var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "SeedData");
  424. if (!Directory.Exists(backendPath))
  425. Directory.CreateDirectory(backendPath);
  426. return Path.Combine(backendPath, input.SeedDataName + ".cs");
  427. }
  428. }