2024年7月

前言

之前分享中台 Admin.Core 的模块代码生成器,陆续也结合群友们的反馈,完善了一些功能和模板上的优化,而本篇将基于此代码生成器生成一个通用代码生成器模块的基本代码

后续再在此代码的基础上进行完善,制作一个通用的代码生成器

要做一个项目,首先我们要弄清楚需求,这里简单规划了下所需要的功能模块

可从脑图中看到,基本除了项目生成这块需要添加代码,相关模块的增删改查都可以由代码生成器来生成

准备

本文开发完成的项目将放在:
yimogit/Emo.Dev

本篇开发代码在我 Fork 的仓库
yimogit/Admin.Core

本文主要的阅读者:开发完成后的代码生成器的使用者,使用/将要用 Admin.Core 框架的猿们,如果觉得有用,欢迎点赞留言

实现

  • 效果一览

超大gif

项目代码准备

git clone https://github.com/yimogit/Admin.Core.git

在运行项目后连接到数据库执行 sql 创建

--添加视图
INSERT INTO ad_view (Id,ParentId,Name,Label,"Path",Description,Cache,Sort,Enabled,CreatedUserId,CreatedUserName,CreatedUserRealName,CreatedTime,ModifiedUserId,ModifiedUserName,ModifiedUserRealName,ModifiedTime,IsDeleted) VALUES
         (558530915442693,0,NULL,'代码管理',NULL,NULL,0,99,1,161223411986501,'admin','管理员','2024-06-16 16:08:08',161223411986501,'admin','管理员','2024-06-16 16:08:16',0),
         (558531082424325,558530915442693,'codegen','代码生成','dev/codegen/index',NULL,0,1,1,161223411986501,'admin','管理员','2024-06-16 16:08:49',NULL,NULL,NULL,NULL,0);
-- 添加权限
INSERT INTO ad_permission (Id,ParentId,Label,Code,"Type",ViewId,Name,"Path",Redirect,Icon,Hidden,Opened,NewWindow,"External",IsKeepAlive,IsAffix,Link,IsIframe,Sort,Description,Enabled,CreatedUserId,CreatedUserName,CreatedUserRealName,CreatedTime,ModifiedUserId,ModifiedUserName,ModifiedUserRealName,ModifiedTime,IsDeleted) VALUES
         (558531409440773,0,'代码生成',NULL,2,558531082424325,'devgen','/codegen',NULL,'ele-Memo',0,0,0,0,0,0,NULL,0,5,NULL,1,161223411986501,'admin','管理员','2024-06-16 16:10:09',161223411986501,'admin','管理员','2024-06-16 16:10:55',0);

或根据前文
中台框架模块开发实践-代码生成器的添加及使用
在前台添加代码生成器菜单

生成器的生成方式说明

代码生成器支持DBFirst和CodeFirst两种方式

  1. DBFirst 需要选择数据源并选择数据表进行生成配置的保存

  1. CodeFirst 可以直接生成配置,配置保存后再使用生成迁移SQL或者将迁移执行到数据库的方式同步数据库表信息

这里为了更方便的生成,做了一个 json 的导入导出,可以快速复制/导入分享的配置,下面会贴出配置,直接导入即可,导入时会将id重置,都为新增

建立数据模型并生成代码

通过前面理的需求,可以创建相关模型

  • 模板分组


    • 模板信息实体
    • DevGroup
  • 模板列表


    • 模板信息实体
    • DevTemplate
  • 项目生成


    • 以项目,项目模型,模型字段组成
    • DevProject
    • DevProjectModel
    • DevProjectModelField

对应代码生成器数据如下,提供这几个模型的基础增删改查功能

对应 json 配置,导入即可

[{"id":563489262788677,"authorName":"yimo","tablePrefix":true,"generateType":"2","dbKey":"admindb","dbType":"Sqlite","tableName":"dev_template","namespace":"ZhonTai.Module.Dev","entityName":"DevTemplate","busName":"模板","apiAreaName":"dev","baseEntity":"EntityBase","menuPid":"代码管理","menuAfterText":"管理","backendOut":"E:\\CodeGenOutput\\ZhonTai.Module.Dev","frontendOut":"E:\\CodeGenOutput","dbMigrateSqlOut":"E:\\MigrateSqls","comment":"","usings":"ZhonTai.Module.Dev.Domain.DevGroup","genEntity":false,"genRepository":false,"genService":false,"genAdd":true,"genUpdate":true,"genDelete":true,"genGetList":true,"genSoftDelete":true,"genBatchDelete":true,"genBatchSoftDelete":true,"fields":[{"id":563489262813253,"codeGenId":563489262788677,"dbKey":"","columnName":"Name","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"模板名称","isPrimary":false,"isNullable":false,"length":"200","editor":"el-input","position":1,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"0","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563489262813254,"codeGenId":563489262788677,"dbKey":"","columnName":"GroupId","columnRawName":null,"netType":"long","dbType":"","comment":"","defaultValue":"","title":"模板分组","isPrimary":false,"isNullable":false,"length":"64","editor":"my-bussiness-select","position":2,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"DevGroupEntity","includeMode":0,"includeEntityKey":"Name","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563489262813255,"codeGenId":563489262788677,"dbKey":"","columnName":"Path","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"生成路径","isPrimary":false,"isNullable":true,"length":"500","editor":"el-input","position":3,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563489262813256,"codeGenId":563489262788677,"dbKey":"","columnName":"Content","columnRawName":null,"netType":"string","dbType":"text","comment":"","defaultValue":"","title":"模板内容","isPrimary":false,"isNullable":false,"length":"-1","editor":"my-input-textarea","position":5,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"}]},{"id":563487794356293,"authorName":"yimo","tablePrefix":true,"generateType":"1","dbKey":"admindb","dbType":"Sqlite","tableName":"dev_group","namespace":"ZhonTai.Module.Dev","entityName":"DevGroup","busName":"模板组","apiAreaName":"dev","baseEntity":"EntityBase","menuPid":"代码管理","menuAfterText":"管理","backendOut":"E:\\CodeGenOutput\\ZhonTai.Module.Dev","frontendOut":"E:\\CodeGenOutput","dbMigrateSqlOut":"E:\\MigrateSqls","comment":"","usings":null,"genEntity":true,"genRepository":true,"genService":true,"genAdd":true,"genUpdate":true,"genDelete":true,"genGetList":true,"genSoftDelete":true,"genBatchDelete":true,"genBatchSoftDelete":true,"fields":[{"id":563487794380869,"codeGenId":563487794356293,"dbKey":"","columnName":"Name","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"模板组名称","isPrimary":false,"isNullable":false,"length":"200","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"0","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563487794380870,"codeGenId":563487794356293,"dbKey":"","columnName":"Remark","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"备注","isPrimary":false,"isNullable":true,"length":"500","editor":"my-input-textarea","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"}]},{"id":563486126821445,"authorName":"yimo","tablePrefix":true,"generateType":"2","dbKey":"admindb","dbType":"Sqlite","tableName":"dev_project_model_field","namespace":"ZhonTai.Module.Dev","entityName":"DevProjectModelField","busName":"项目模型字段","apiAreaName":"dev","baseEntity":"EntityBase","menuPid":null,"menuAfterText":"管理","backendOut":"E:\\CodeGenOutput\\ZhonTai.Module.Dev","frontendOut":"E:\\CodeGenOutput","dbMigrateSqlOut":"E:\\MigrateSqls","comment":"","usings":"ZhonTai.Module.Dev.Domain.DevProjectModel","genEntity":false,"genRepository":false,"genService":false,"genAdd":true,"genUpdate":true,"genDelete":true,"genGetList":true,"genSoftDelete":true,"genBatchDelete":true,"genBatchSoftDelete":true,"fields":[{"id":563486126850117,"codeGenId":563486126821445,"dbKey":"","columnName":"Name","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"字段名称","isPrimary":false,"isNullable":false,"length":"200","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563486126850118,"codeGenId":563486126821445,"dbKey":"","columnName":"Description","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"字段描述","isPrimary":false,"isNullable":true,"length":"-1","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563486126850119,"codeGenId":563486126821445,"dbKey":"","columnName":"DataType","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"字段类型","isPrimary":false,"isNullable":true,"length":"64","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563486126850120,"codeGenId":563486126821445,"dbKey":"","columnName":"IsRequired","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"是否必填","isPrimary":false,"isNullable":true,"length":"64","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563486126850121,"codeGenId":563486126821445,"dbKey":"","columnName":"MaxLength","columnRawName":null,"netType":"int","dbType":"","comment":"","defaultValue":"","title":"最大长度","isPrimary":false,"isNullable":true,"length":"-1","editor":"el-input-number","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563486126850122,"codeGenId":563486126821445,"dbKey":"","columnName":"MinLength","columnRawName":null,"netType":"int","dbType":"","comment":"","defaultValue":"","title":"最小长度","isPrimary":false,"isNullable":true,"length":"-1","editor":"el-input-number","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563492098199621,"codeGenId":563486126821445,"dbKey":"","columnName":"ModelId","columnRawName":null,"netType":"long","dbType":"","comment":"","defaultValue":"","title":"模型Id","isPrimary":false,"isNullable":false,"length":"64","editor":"my-bussiness-select","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"DevProjectModel","includeMode":0,"includeEntityKey":"Name","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"}]},{"id":563484091809861,"authorName":"yimo","tablePrefix":true,"generateType":"2","dbKey":"admindb","dbType":"Sqlite","tableName":"dev_project_model","namespace":"ZhonTai.Module.Dev","entityName":"DevProjectModel","busName":"项目模型","apiAreaName":"dev","baseEntity":"EntityBase","menuPid":"代码管理","menuAfterText":"管理","backendOut":"E:\\CodeGenOutput\\ZhonTai.Module.Dev","frontendOut":"E:\\CodeGenOutput","dbMigrateSqlOut":"E:\\MigrateSqls","comment":"","usings":"ZhonTai.Module.Dev.Domain.DevProject","genEntity":false,"genRepository":false,"genService":false,"genAdd":true,"genUpdate":true,"genDelete":true,"genGetList":true,"genSoftDelete":true,"genBatchDelete":true,"genBatchSoftDelete":true,"fields":[{"id":563484091834437,"codeGenId":563484091809861,"dbKey":"","columnName":"Name","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"模型名称","isPrimary":false,"isNullable":false,"length":"200","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"0","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563484091834438,"codeGenId":563484091809861,"dbKey":"","columnName":"Code","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"模型编码","isPrimary":false,"isNullable":false,"length":"200","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"0","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563484091834439,"codeGenId":563484091809861,"dbKey":"","columnName":"Remark","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"备注","isPrimary":false,"isNullable":true,"length":"-1","editor":"my-input-textarea","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563484091834440,"codeGenId":563484091809861,"dbKey":"","columnName":"ProjectId","columnRawName":null,"netType":"long","dbType":"","comment":"","defaultValue":"","title":"所属项目","isPrimary":false,"isNullable":false,"length":"64","editor":"my-bussiness-select","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"DevProject","includeMode":0,"includeEntityKey":"Name","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"}]},{"id":563483673227333,"authorName":"yimo","tablePrefix":true,"generateType":"2","dbKey":"admindb","dbType":"Sqlite","tableName":"dev_project","namespace":"ZhonTai.Module.Dev","entityName":"DevProject","busName":"项目","apiAreaName":"dev","baseEntity":"EntityBase","menuPid":"代码管理","menuAfterText":"管理","backendOut":"E:\\CodeGenOutput\\ZhonTai.Module.Dev","frontendOut":"E:\\CodeGenOutput","dbMigrateSqlOut":"E:\\MigrateSqls","comment":"","usings":"","genEntity":false,"genRepository":false,"genService":false,"genAdd":true,"genUpdate":true,"genDelete":true,"genGetList":true,"genSoftDelete":true,"genBatchDelete":true,"genBatchSoftDelete":true,"fields":[{"id":563483673256005,"codeGenId":563483673227333,"dbKey":"","columnName":"Name","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"项目名称","isPrimary":false,"isNullable":false,"length":"200","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"0","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563483673284677,"codeGenId":563483673227333,"dbKey":"","columnName":"Code","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"项目编码","isPrimary":false,"isNullable":false,"length":"200","editor":"el-input","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":true,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"0","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"},{"id":563483673284678,"codeGenId":563483673227333,"dbKey":"","columnName":"Remark","columnRawName":null,"netType":"string","dbType":"","comment":"","defaultValue":"","title":"备注","isPrimary":false,"isNullable":true,"length":"-1","editor":"my-input-textarea","position":0,"whetherCommon":false,"whetherRetract":false,"whetherQuery":false,"whetherAdd":true,"whetherUpdate":true,"whetherTable":true,"whetherList":true,"indexMode":null,"isUnique":false,"queryType":"6","dictTypeCode":"","includeEntity":"","includeMode":0,"includeEntityKey":"","displayColumn":"","valueColumn":"","pidColumn":"","effectType":"input","frontendRuleTrigger":"blur"}]}]

生成模型一览

模板的模型配置参考

生成的后端代码(E:\CodeGenOutput\ZhonTai.Module.Dev)

前端代码( E:\CodeGenOutput\views)

在 Admin.Core 中使用生成的代码

如果已经有项目可以直接添加对库的引用,这里直接新建一个项目 Emo.Dev

dotnet new install ZhonTai.Template::8.2.0
dotnet new MyApp -n Emo.Dev

可以看到这里只生成了后端接口,目前 v8.2.0 还不支持前端代码的生成,所以我们还需要手动去将对应版本的
前端代码
下载一份到项目中(只保留),并调整下目录结构,前端代码放到 admin-ui ,后端代码放到 admin-api

运行前后端项目,确认项目运行没问题后开始添加通用代码生成器模块代码。后续任意模块代码都可以参考步骤

1.后端项目引用关系配置

  • 将生成的模块代码 ZhonTai.Module.Dev 拷贝到在新项目中

修改库中的引用,默认生成的 ZhonTai.Module.Dev.csproj 引用是相对源码的路径

  • 所以需要修改下,直接引用 ZhonTai.Admin 的包
  <ItemGroup>
    <PackageReference Include="ZhonTai.Admin" Version="$(PackageZhonTaiVersion)">
      <CopyToOutputDirectory>***.xml</CopyToOutputDirectory>
    </PackageReference>
  </ItemGroup>
  • Host 项目添加对 ZhonTai.Module.Dev 的引用

  • 签名文件未找到报错处理

添加完成后如果提示前面文件找不到,可将Api下的签名文件复制过来或自行生成

我这里是将Emo.Dev.Api的key.snk文件复制到了platform文件夹,并改了02.build/Directory.Build.props 中程序集签名的文件路径到 ..\kye.snk ,这样就可以让项目中的所有程序集就用这个前面文件

  • 整体修改情况

2.后端配置修改

  • 修改应用配置: ConfigCenter/appconfig.json


    • 修改程序集名称和swagger配置
    • //程序集名称
      "assemblyNames": [ "Emo.Dev.Api", "ZhonTai.Admin", "ZhonTai.Module.Dev" ],
      //Swagger接口文档
      "swagger": [
          ...
          {
            "name": "代码生成",
            "code": "dev",
            "version": "v8.2.0",
            "description": "代码生成"
          }
          ...
      ]
      

  • 修改数据库配置: ConfigCenter/dbconfig.json


    • 增加dbs配置节点:指定数据库的key,assemblyNames,type,connectionString
    • 默认将创建实体表对应的数据表

生成的数据库表

修改后配置后 Ctrl+F5运行即可查看代码生成接口

3.前端项目配置

  • 先安装包
    pnpm install
  • 复制生成的代码 views 到前端项目

  • 修改并执行 gen/gen-api.js 生成前端项目文件

增加正确的后端swagger地址,当前版本默认的gen-api.js有问题需要修改路由前缀,如图所示,如果要将/app修改为其他的,几个地方都要修改

执行:
npm run gen:api
进行接口文件生成

  • 运行项目:
    npm run dev
    开始配置菜单和视图

4.系统中配置菜单

  1. 同步模块接口

  1. 添加视图地址
  • 父级视图可以不添加命名,参考其他的添加即可
  • 视图地址和views下文件路径对应

  1. 添加分组及菜单
  • 【分组】为一级菜单,【菜单】为二级菜单,【权限点】对应为按钮
  • 默认选择了视图将会自动带出视图信息,直接保存接口

  1. 配置权限点

这里需要注意,选择api接口时点击图标咱开下级,否则将会选中父级的编码和名称

当然,也可以自行改下代码 让api接口选中的时候判断最下级才选中

配置完成如下所示

  1. 刷新页面即可访问到新添加的代码管理模块
  • 如果创建后访问报错,大概率是因为还没有初始化数据库
  • 访问示例

5.复制生成器附带的样式

因为在做代码生成器的时候为了生成更舒服一点,添加了一些通用的样式,但是没有提交到主库,所以这里可以将我的
自定义样式
复制到自己的项目中

至此,成功将生成的代码应用到独立项目中

后语

花了一天的时间重新整理了代码生成器的使用,将其应用到一个独立项目中,在此期间也发现框架的一些问题,给作者提了PR,也在这个过程中学到许多

对于Admin.Core框架,总体来讲使用起来还是很顺畅的,代码也比较好阅读,简单的 crud 结合代码生成器,做一些小项目,完全是够用的了

相关文档

大家好,我是码农先森。

说到 HTTP 请求工具想必对我们做 Web 开发的程序员都不陌生,只要涉及到网络请求都必须使用。对于我们 PHP 程序员来说,最熟悉不过的就是 CURL 扩展,只要安装的这个扩展便可随意发起 HTTP 请求。 但在 PHP 语言中还有一个很好用的 Composer 包「guzzlehttp/guzzle」,相信大家只要做 PHP 开发的都接触过。

转到 Go 语言后,有些人也会在想 Go 中有没有这样的工具包呢?说实话,我从事 Go 语言开发这么挺久,也始终没有发现有类似 guzzle 这么好用的工具包。我想其中的原因可能是 Go 语言的官方标准库中提供了「net/http」工具,这个也能满足日常的开发需求,大家也就没有想再造轮子的兴趣了。话不多说,接下来我们开始介绍在 PHP 语言和 Go 语言中的使用姿势。

在 php_http 目录下中使用 Composer 命令安装 guzzle 包。

[manongsen@root ~]$ pwd
/home/manongsen/workspace/php_to_go/php_http
[manongsen@root php_http]$ composer require guzzlehttp/guzzle

在 go_http 目录下中使用 go mod init 初始化 Go 项目。

[manongsen@root ~]$ pwd
/home/manongsen/workspace/php_to_go/go_http
[manongsen@root go_http]$ go mod init go_http

这是初始化完成后的项目结构。

[manongsen@root php_to_go]$ tree -L 2
.
├── README.md
├── go_http
│   ├── go.mod
│   ├── go.sum
│   └── main.go
└── php_http
    ├── composer.json
    ├── composer.lock
    ├── index.php
    └── vendor

这次我们主要介绍最常用的两种请求方法,分别是 GET 请求和 POST 请求。

在 PHP 中先通过 require 加载到 GuzzleHttp 类库,然后实例化 \GuzzleHttp\Client 类,并通过构造函数设置 base_uri 和 timeout 参数,再基于 $client 对象发起 GET 请求和 POST 请求,在使用过程中需要注意捕获异常情况。

// php_to_go/php_http/index.php
<?php

require 'vendor/autoload.php'; 

try {
    // 构建请求客户端
    $client = new GuzzleHttp\Client([
        // 设置基础的请求地址
        'base_uri' => 'https://www.baidu.com',
        // 设置超时时间
        'timeout'  => 3.0,
    ]);

     // -----------------GET 请求-----------------------
    $response = $client->request('GET', '/', [
        'query' => ['wd' => 'php'] // 设置搜索参数
    ]);
    // 获取 HTTP 响应状态码
    print_r($response->getStatusCode()); 
    // 获取 HTTP 响应数据体
    print_r($response->getBody()); 

    // -----------------POST 请求-----------------------
    $response = $client->request('POST', '/', [
        'json' => ['wd' => 'php'] // 设置搜索参数
    ]);
    // 获取 HTTP 响应状态码
    print_r($response->getStatusCode()); 
    // 获取 HTTP 响应数据体
    print_r($response->getBody()); 
} catch (GuzzleHttp\Exception\RequestException $e) {
    // 捕获请求异常
    echo "Error: " . $e->getMessage();
}

从上面的代码可以看出在 PHP 中发起 HTTP 请求使用起来还是很简单的。那么在 Go 中 GET 和 POST 的使用方法是怎样的呢?从下面的代码量上来看就要多很多了,先要 import 相关的标准库,比如 net/http、encoding/json 等。

其次定义一个 http.Client{} 结构体的对象,并且设置 HTTP 请求的超时时间。然后再调用 client.Get() 或 client.Post() 方法来发起 GET 及 Post 请求。最后再使用 ioutil.ReadAll 方法从 response.Body 网络数据流中读取出 HTTP 请求体数据,读取完数据之后要记得关闭资源。这里需要注意的一点是 response.Body 网络数据流是不能重复读取,初学者这里是容易犯错的点。

// php_to_go/go_http/main.go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
)

func main() {
	// 定义 基础的请求地址
	baseUrl := "https://www.baidu.com"

	// 创建一个 HTTP 客户端
	client := &http.Client{
		Timeout: 3 * time.Second, // 设置超时时间为 3 秒
	}

	// ----------------------GET请求--------------------------
	// 构建请求参数
	params := url.Values{}
	params.Add("wd", "go")

	// 发起 GET 请求
	response, err := client.Get(baseUrl + params.Encode())
	if err != nil {
		fmt.Printf("发起请求时出现错误: %v\n", err)
		return
	}
	// 关闭资源 防止资源泄露
	defer response.Body.Close()

	// 读取 HTTP 响应体数据
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		fmt.Printf("读取响应时出现错误: %v\n", err)
		return
	}

	// 输出 HTTP 响应状态码
	fmt.Printf("status: %v\n", response.Status)
	// 输出 HTTP 响应体数据
	fmt.Printf("body: %v\n", string(body))

	// ----------------------POST请求--------------------------
	// 定义请求的参数
	reqParams := map[string]string{
		"wd": "go",
	}
	jsonData, err := json.Marshal(reqParams)
	if err != nil {
		fmt.Printf("JSON 编码错误: %v", err)
		return
	}

	// 创建一个新的字节缓冲区并将 jsonData 数据放入缓存区
	reqBody := bytes.NewBuffer(jsonData)

	// 发起 POST 请求,请求的内容格式为 JSON 格式
	response, err = client.Post(baseUrl, "application/json", reqBody)
	if err != nil {
		fmt.Printf("发起请求时出现错误: %v", err)
		return
	}
	// 关闭资源 防止资源泄露
	defer response.Body.Close()

	// 读取 HTTP 响应体数据
	body, err = ioutil.ReadAll(response.Body)
	if err != nil {
		fmt.Printf("读取响应时出现错误: %v", err)
		return
	}

	// 输出 HTTP 响应状态码
	fmt.Printf("status: %v\n", response.Status)
	// 输出 HTTP 响应体数据
	fmt.Printf("body: %v\n", string(body))
}

从使用的角度来看 PHP 比 Go 要简单的多,异常捕获使用 try catch{} 即可,反观 Go 中要通过 if != err {} 来反复的判断是否有错误,略显繁琐。同时在这个例子中也就可以看得出 php 的开发效率比 go 要快的多,go 中要时刻注意变量的类型、数据的读取、资源的关闭等情况,php 则一梭子就开发完了。

但 Go 的执行效率可比 PHP 高的多,Go 作为静态语言直接打包成了二进制可执行文件,而 PHP 是动态语言执行时才开始解析代码。因此两者刚好互补,学习 Go 也更利于我们 PHPer 了解系统资源、网络请求、缓存区等的使用方式,这些都是在 PHP 中很少能接触到的。

对于 HTTP 工具的使用姿势,其实最重要的是要掌握 Header 头各种参数的配置,比如 Content-Type、Proxy、Authorization、Cookies、Cert 等,那么我这里在把 GuzzleHttp 和 net/http 的官方文档附上,方便大家更加深入的学习。

推荐


欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

开心一刻

暗恋公司的一个女同事,聊了快一年了,一直没勇气表白

上个月突然找我借 5000 块钱,我直接转给她了

我:这钱干嘛用的?

她:给男朋友买个手机

我强颜欢笑说:你真贴心

几天后我收到一个快递,打开一看是部手机!!!

我压抑着内心的激动,放下手头的工作,立马微信上问她怎么回事

她说:手机她男朋友不喜欢,商家也不给退,就当还我钱了……

杀人诛心

需求描述

一天,领导找到我,巴拉巴拉一顿沟通,需求很清晰,我就不细说了(因为跟本文关系不大),总结下就是

有两个服务:任务分发中心、任务执行器
分发中心负责任务拆分,然后将拆分后的任务下发给执行器,执行器执行任务
任务分发中心和任务执行器都要支持水平扩节点

我说:技术实现没什么难点,引入注册中心就行

但领导紧接着就说:考虑到客户硬件资源的局限性以及部署的复杂性,不能单独部署注册中心

当时我就懵了,心里想的是

既要马儿跑,又要马儿不吃草,咋不让我上天?

工作要紧

我接着说道:任务分发中心兼职注册中心,任务执行器能够自动注册到分发中心?

领导:对,你总结的言简意赅,就是要实现这样的效果

领导又补充道:你可以简单点实现,不用像
Nacos
那样复杂,服务支持水平扩展,能够监控任务执行器状态就行

相信大家都明白需求了,总结成一句话

拿着卖白菜的钱,要干卖白粉的事!

需求实现

主流的注册中心,像
Zookeeper

Nacos

etcd、

Consul
以及
Eureka
,都需要单独部署;如果能单独部署,那实现就简单了,可现在要求是不能单独部署,但又要有注册中心的基本功能

宝宝心里苦

说白了,就是要自实现一个简易版的注册中心,虽说是简易版,但基本功能还是要有吧

  1. 服务发现
    注册中心存储和管理所有可用服务的地址信息,其他服务可以通过查询注册中心发现所需的服务
  2. 服务注册
    服务提供者可以将自己的地址信息注册到注册中心,以便其他服务发现和调用
  3. 健康检查
    注册中心定期对注册的服务进行健康检查,以确保服务的可用性
    某个服务出现故障或不可用,注册中心能够将其从可用服务列表中移除,防止其他服务调用失败
  4. 支持水平拓展
    注册中心肯定要支持水平拓展来保障其高可用
    注册中心节点之间要能正常通信,节点之间的可用服务列表能够及时同步,并且要保证一致

这些基本功能实现起来简不简单?也许你们觉的简单,可我觉的并不简单

但事已至此,已无退路,只能硬着头皮上了

分发中心集成 Eureka Server

几个主流的注册中心,我对
Eureka
比较熟悉,感觉其实现还算比较简单,参考它来实现注册中心应该可行

一头扎进去看源码,一天下来收效甚微,发现并非想象中的那么简单,正当我准备放弃时,脑中闪过一个念头

任务分发中心可不可以集成 Eureka Server?

Eureka Server
是注册中心,这个我们都知道,但我们忽略了一个点,它还是一个普通 Web 服务,只是我们平时不用它来处理业务请求,仅仅是把它当做一个注册中心

从定位来讲,
Eureka Server
确实只应该做注册中心该做的事,不应该关联到业务,尤其是服务多时,
Eureka Server
维护压力大,更不应该关联业务

但有时候也需要特殊情况特殊处理,比如我目前的需求,注册中心维护的服务就一个
任务执行器
,任务执行器的节点也不会很多,顶天 10 个,也就是
Eureka Servr
维护服务的压力会很小,同时业务处理也很简单:
任务拆分,然后分发

这么分析下来,任务分发中心集成
Eureka Server
是可行的,说干就干!

干就完了

Eureka server
搭建不要太简单,你们肯定都会

https://gitee.com/youzhibing/qsl-project/tree/master/integrate-eureka

搭建过程就不演示了,偷懒的小伙伴可以直接用我提供的代码

此时的代码结构如下(未包含业务代码)

代码结构

搭建完成后启动服务,
Eureka
监控如下

eureka搭建完成

至此,
Eureka
集成完成

你们发现没,前面的
Eureka
搭建跟我们平时的
Eureka
搭建是不是没区别?

肯定没区别呀,我就是按平时的搭建流程来的,具体区别还要往下看

分发中心集成 Eureka Client

为了更好的体现
任务分发中心
集成
Eureka Client
,我们简化业务实现

任务执行器
就提供一个接口给
任务分发中心
调用

任务执行器接口_exec

任务分发中心
也提供一个接口给其他服务(例如
调度中心
)调用

分发中心接口

重点来了,任务分发中心如何将拆分后的任务分发到任务执行器?

细化来讲,分 3 点
1.任务分发中心能不能拿到任务执行器服务列表
2.能拿到的话,如何拿
3.拿到之后如何负载均衡的下发任务

第 1 点毋庸置疑,肯定能拿到,任务分发中心作为
Eureka Server
,存储了任务执行器的服务实例列表

第 2 、3点,我们可以将
任务分发中心
又当做
Eureka Client
试试,试试有不要钱

  1. 引入依赖
    Eureka Server
    包含了
    Eureka Client
    ,不需要额外引入
    Eureka Client
    但需要引入
    open-feign

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 开启
    Eureka Client

    open-feign

    @SpringBootApplication
    @EnableEurekaServer
    @EnableEurekaClient
    @EnableFeignClients
    public class TaskDispatcherApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(TaskDispatcherApplication.class, args);
        }
    }
    
  3. 实现
    TaskExecutorService
    ,完成对
    任务执行器
    负载均衡调用

    @FeignClient("TASK-EXECUTOR")
    public interface TaskExecutorService {
    
        @GetMapping("task/exec")
        String exec(@RequestParam("taskId") Long taskId);
    }
    

    @GetMapping("dispatch")
    public String dispatch(Long jobId) {
    	LOGGER.info("收到作业[jobId={}]执行请求", jobId);
    	LOGGER.info("作业[jobId={}]拆分任务中...", jobId);
    	List<Long> taskIds = Arrays.asList(123L, 666L, 888L, 999L);
    	LOGGER.info("作业[jobId={}]拆分完成,得到作业列表[{}]", jobId, taskIds);
    	for (Long taskId : taskIds) {
    		String execResult = taskExecutorService.exec(taskId);
    		LOGGER.info("任务[{}]执行结果:{}", taskId, execResult);
    	}
    	return "success";
    }
    

启动
task-dispatcher

task-executor
,然后调用接口

http://192.168.2.10:8080/dispatcher/job/dispatch?jobId=689

你会发现
500

500

看下日志,关键信息如下

Load balancer does not contain an instance for the service TASK-EXECUTOR

负载均衡器不包含
TASK-EXECUTOR
服务实例,这是因为我们有个配置关闭了

fetch-registry-false

导致
Eureka Client
未拉取其他服务,那么
Load balancer
自然就获取不到
TASK-EXECUTOR
实例了

我们将其开启(设置成
true
),然后启动
task-dispatcher

task-executor
,然后调用接口

http://192.168.2.10:8080/dispatcher/job/dispatch?jobId=689

结果
success
,日志输出如下

  1. task-dispatcher

    2024-06-30 10:51:51.653|INFO|http-nio-8080-exec-3|25|com.qsl.task.web.JobController          :收到作业[jobId=689]执行请求
    2024-06-30 10:51:51.653|INFO|http-nio-8080-exec-3|26|com.qsl.task.web.JobController          :作业[jobId=689]拆分任务中...
    2024-06-30 10:51:51.653|INFO|http-nio-8080-exec-3|28|com.qsl.task.web.JobController          :作业[jobId=689]拆分完成,得到作业列表[[123, 666, 888, 999]]
    2024-06-30 10:51:51.657|INFO|http-nio-8080-exec-3|31|com.qsl.task.web.JobController          :任务[123]执行结果:success
    2024-06-30 10:51:51.660|INFO|http-nio-8080-exec-3|31|com.qsl.task.web.JobController          :任务[666]执行结果:success
    2024-06-30 10:51:51.663|INFO|http-nio-8080-exec-3|31|com.qsl.task.web.JobController          :任务[888]执行结果:success
    2024-06-30 10:51:51.665|INFO|http-nio-8080-exec-3|31|com.qsl.task.web.JobController          :任务[999]执行结果:success
    
  2. task-executor

    2024-06-30 10:51:51.656|INFO|http-nio-8081-exec-6|17|com.qsl.task.web.TaskController         :收到任务[taskId=123]执行请求
    2024-06-30 10:51:51.656|INFO|http-nio-8081-exec-6|18|com.qsl.task.web.TaskController         :任务[taskId=123]执行中...
    2024-06-30 10:51:51.656|INFO|http-nio-8081-exec-6|19|com.qsl.task.web.TaskController         :任务[taskId=123]执行完成
    2024-06-30 10:51:51.659|INFO|http-nio-8081-exec-9|17|com.qsl.task.web.TaskController         :收到任务[taskId=666]执行请求
    2024-06-30 10:51:51.659|INFO|http-nio-8081-exec-9|18|com.qsl.task.web.TaskController         :任务[taskId=666]执行中...
    2024-06-30 10:51:51.659|INFO|http-nio-8081-exec-9|19|com.qsl.task.web.TaskController         :任务[taskId=666]执行完成
    2024-06-30 10:51:51.662|INFO|http-nio-8081-exec-8|17|com.qsl.task.web.TaskController         :收到任务[taskId=888]执行请求
    2024-06-30 10:51:51.662|INFO|http-nio-8081-exec-8|18|com.qsl.task.web.TaskController         :任务[taskId=888]执行中...
    2024-06-30 10:51:51.662|INFO|http-nio-8081-exec-8|19|com.qsl.task.web.TaskController         :任务[taskId=888]执行完成
    2024-06-30 10:51:51.664|INFO|http-nio-8081-exec-7|17|com.qsl.task.web.TaskController         :收到任务[taskId=999]执行请求
    2024-06-30 10:51:51.664|INFO|http-nio-8081-exec-7|18|com.qsl.task.web.TaskController         :任务[taskId=999]执行中...
    2024-06-30 10:51:51.664|INFO|http-nio-8081-exec-7|19|com.qsl.task.web.TaskController         :任务[taskId=999]执行完成
    

目前
task-executor
是单实例,我们再启动一个实例,来看看负载均衡效果

负载均衡

至此,需求是不是实现了

愣着干啥,鼓掌

总结

  1. 注册中心推荐单独部署,不要掺杂业务代码
  2. Eureka Server
    也可以做
    Eureka Client
    ,虽说不推荐,但有时候能止渴
  3. 要敢于尝试,不试怎么知道不行?

最近发现越来越多的小伙伴被公司裁员,有的是因为公司业绩不景气被裁员,有的是因为压力太大离职。很多公司都在裁人、减员。找工作也比之前难。刚好去年我也被上家裁员了,正好做一个系列的日志,希望能帮到在找工作的你。

本文为第一篇失业日记:工作五年,交接只需要半天。

上午敲代码,下午HR谈话

离职那天天,一切如往常一样,9点到公司,打开电脑,继续撸昨天的代码,中午去楼下的饭店吃个饭。直到下午的时候,HR 通知我去会议室,那个时候就感觉不太对劲,就预感要裁员了,因为之前公司也在开始裁员了。那个时候还和同事开玩笑说:估计要被裁了。

一开始 HR 就开始嘘寒问暖的问我:最近工作怎么样?我就猜到了基本就是裁员了,后续就按照流程走,谈赔偿,以及近期的离职交接。赔偿正常的赔偿标准,也符合我的心里预期。谈好赔偿之后,把一些琐碎的事情整理好之后,后面就交接工作。

当时以为在公司干了五年多,做的东西也比较多,还以为交接需要几天,结果被告知下午就交接完。
花了一下午就完成的工作交接,收拾好自己的东西。约了几个同事在附近吃个饭,回忆之前在公司的一些美好的回忆,想着自己未知的未来。吃完饭之后,就和这个工作了几年的公司说再见了。

离职的第一天

工作后的只要是工作日基本都是起床、赶地铁、到公司上班的节奏。一下子忽然不用上班,就像是已经习惯了一种东西,突然就消失了。

从记事开始,我们就要按照固定的模式生活,到了年龄就该读书,读了十多年的书之后,就要去工作,日复一日的工作,工作一下忽然停止了,就无所适从了。可能大部分的第一次离职或者被裁员小伙伴都有这种感受。

不适应之后就开始爽了,因为裁员赔了 n+1 的工资。大概可以半年可以不用工作。每天也不需要到点就起床挤地铁上班,也不需要应付各种工作任务。每天都是想睡到几点就可以睡到几点。之前在公司都是自己带午饭的,每次做饭都是算好时间比如大概7点到家,大概一个小时做好饭,8点做好。而离职后就很随意,可以随便选一个时间,精心的做一个午餐或者晚餐。相对固定繁琐的工作来说。
离职果然是最好的养生。想做的事情按照自己的节奏做,不想做的事情就可以不做。

下午去附近的公园转了转,身体放松之后,做什么事情都变得的开心,去接触大自然,看看花花草草、树木森林。之前一直不懂为啥长假回老家就很舒服,就是因为暂时可以不用想工作的事情。

为啥裁员这么快

在一家公司待久了,就会有一种错觉,觉得自己的工作挺重要。其实等到裁员的时候,可能连工作都不需要交接,马上就会让你走人。对于公司来说,核心员工是极小的,大部分都是执行层,而执行层基本是按照引导做事,换一个人对公司来说没有多大的影响。

比如一个工具类,在系统已经运行了很多年,一直也没有什么问题。但是后面出了一个更好的工具类,书写更简单、安全性还更高。大部分的人选择就是直接替换掉。那反推回来,裁员也是一样的道理,如果一个员工自身不进步、或者每天都是做同样的事情的时候,这个时候有一个更好的选择的时候,企业都会去选择更好的,这个更好的选择不单单指的是替换人,而是现在的经济形势下开源节流。

这里不去说决策的对错,每个企业都有它自己的运行策略,对于我们个人来说,也是需要有一个自己的运行策略。把自己也当做一个企业不断的进步和成长。减少一种工具的属性。

提高技术能力

工作能力是一起的前提,如果工作都做不好,就更不要想着更远的事情。很多人被裁是因为工作产出不够,每天工作不认真,上班摸摸鱼,到点就下班。这种一般在公司里面的薪资比较偏低的,私企公司大部分都是靠能力说话,能力不足,自然收入不比较低。就拿我公司另外一个部门的同事来说,员工 A 和员工 B 都是一个岗位的,A 的收入和能力都在 B 之上。A 出活快,B 能力差,做事效率也慢。所以即使 A 的工资明显高于 B,但是后面加薪基本是 A 员工加薪。
在一个公司加薪是需要有业绩证明,而不是需要低工资证明。
有了更好的能力之后,即使在这个公司无法涨薪,跳槽涨薪也更快的。

及时复盘总结

之前说过,放松时候的状态,才是人最舒服的状态,其实也是思考最深的状态。很多人即使在公司做了很多事情,也是很多繁琐、琐碎的事情。做工作总结最大的好处就是打破自我感觉良好的错觉,你以为你努力、勤奋、工作出色,当总结工作之后,你就会发现忙来忙去基本都是从同样的任务,也没有创造多大的价值。而面试时候经常问在上家公司的工作经历,其实就是问你的总结能力和思考能力。如果你对自己的工作,事后有产出、有总结、有思考,这样子面试成功的概率就高了很多。因为大部分人都只是忙碌的工作,每天加班到很晚,下班之后就想着放松玩游戏之类的。玩游戏确实很舒服,做自己熟悉的工作也很舒服。但是都做同样的事情,如何让你在面试中脱颖而出呢?

在2023年的2月20日,在WPF的讨论区,WPF团队对路线的优先级发起了一次讨论。

image-20240701081345233

image-20240701081703274

对三个事项发起了投票。

第一个是Windows 11 主题

image-20240701082306885

第二个是更新的控件

image-20240701082449110

第三个是可空性注释

image-20240701082722449

最终Windows 11 主题得票最高,WPF团队2023-2024的工作优先级就是Windows 11 主题了。WPF 控件的外观多年来一直没有改变。WPF团队相信,更新WPF的样式以匹配 Windows 11 中使用的样式将帮助 WPF 开发人员创建更一致的 Windows 体验。

在2023年12月13日,WPF团队宣布了将与WPFUI在WPF的Win11 主题进行合作。

image-20240701084325333

在Build 2024上,与WPF有关的有以下两个视频。

1、
Navigating Win32 App Development with WinUI and WPF
(45 min)
使用 WinUI 和 WPF 进行 Win32 应用开发导航(45 分钟)

2、
How to create superior experiences with WinUI and WPF
(45 min)
如何使用 WinUI 和 WPF 创建卓越体验(45 分钟)

在Build 2024上展示了具有Win11 主题的WPF Gallery。

2024年5月17日在微软商店发布了WPF Gallery Preview。

image-20240701085059763

源代码在[WPF-Samples/Sample Applications/WPFGallery at main · microsoft/WPF-Samples (github.com)](
https://github.com/microsoft/WPF-Samples/tree/main/Sample
Applications/WPFGallery)

WPF的Win11主题适用于 .NET 9 Preview 4 及其以上版本。

项目结构如下所示:

image-20240701085517438

将项目的目标框架修改为.NET 9:

image-20240701085554661

运行结果如下所示:

image-20240701085959208

这也是一个很好的学习WPF的项目,可以通过看源代码提升自己的WPF水平。

WPF Gallery演示如何在标记中指定 XAML 控件,因为每个控件页都显示用于创建每个示例的标记。它还将显示您的应用程序的所有可能的布局选项。

WPF Gallery应用还包含有关使用颜色、排版和图标开发特殊应用程序的设计指南。它还包括一个示例页,用于演示如何使用不同的控件在 WPF 应用程序中创建用户界面。

WPF在.NET 9中可以使用Win11主题的控件了,有助于WPF开发者们开发出更符合现在设计风格、更美观的界面。

希望WPF越来越好。