上一篇文章《GAF安装部署-微服务架构下的云原生部署》介绍如何安装部署GAF,这篇文章
首先,介绍了如何调用后端接口,用于进行扩展开发时在后端代码调用GAF已有的接口。
然后,介绍了功能组件扩展的流程,包括后端工程创建、前端工程、前后端联调、后端代码生成、构建部署、配置路由、配置菜单,通过一个简单的案例说明。
最后,介绍了在开发后端代码中使用微服务远程调用需要注意的点,以及如何获取用户信息。
阅读对象是哪些人群?
开发人员和运维人员
什么时候应该阅读?
在熟悉了GAF的功能模块后,需要根据业务进行功能模块的开发,开发完成后进行部署并集成到GAF
在登录GAF后,点击右上角关于,选择api文档
然后,选择认证组件(gaf-authentication), 个性化设置,在Host输入http://${主机ip}/api
并启用。
然后在token接口,选择账号密码获取token
接口,在调试界面输入账户sys_admin和密码123456,获取token。复制返回的"access_token"
然后,选择系统管理组件(gaf-sys-mgt),在全局参数设置里面新增请求头参数,参数名Authorization
,参数值Bearer 上一步复制的token
,参数类型header。
接着,查看个性化设置里面,确认Host 为http://${主机ip}/api
并启用。
最后选择字典接口下的查询字典数据所有树节点
接口,调试调用该接口
如果经过网关调用后端微服务接口,则需要加统一前缀api
,并且加上请求头Authorization
和值Bearer ${申请的token}
。直接调用微服务接口,则无需加前缀api
。token中包含userid和username,下游微服务可通过token获取用户的userid和username。
若通过网关调用,则加api
前缀。可使用postman调用,如下图。
在GAF的开发运维菜单下,点击扩展说明,输入应用名称和应用访问路径前缀,得到根据模板生成前后端工程命令。如下图
linux shell:
mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate \
-DgroupId="com.supermap.extend1" \
-DartifactId="extend1" \
-Dversion="1.0.0-SNAPSHOT" \
-DpackageName="com.supermap.extend1" \
-DgafVersion="3.0.0.pro-alpha-SNAPSHOT" \
-DservicePrefix="extend1" \
-DarchetypeGroupId="com.supermap.gaf.archetype" \
-DarchetypeArtifactId="gaf-spring-rest-archetype" \
-DarchetypeVersion="3.0.0" -DinteractiveMode=false \
-DarchetypeCatalog=https://nexus.gaf.net.cn/repository/maven-public/ \
-Dline.separator=$'\n'
windows power shell:
mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DgroupId="com.supermap.extend1" -DartifactId="extend1" -Dversion="1.0.0-SNAPSHOT" -DpackageName="com.supermap.extend1" -DgafVersion="3.0.0.pro-alpha-SNAPSHOT" -DservicePrefix="extend1" -DarchetypeGroupId="com.supermap.gaf.archetype" -DarchetypeArtifactId="gaf-spring-rest-archetype" -DarchetypeVersion="3.0.0" -DinteractiveMode=false -DarchetypeCatalog=https://nexus.gaf.net.cn/repository/maven-public/ -Dline.separator=$'\n'
生成后端工程,结构如下图。
│ pom.xml # 微服务相关的依赖和gaf相关的依赖
│
└─src
└─main
├─java
│ └─com
│ └─supermap
│ └─extend1
│ │ Application.java # 启动类,包含一些Bean扫描包路径,以及对服务发现客户端和Feign的开启
│ │
│ ├─commontypes
│ │ AppBean.java
│ │
│ ├─controls
│ │ AppRestController.java
│ │
│ └─services
│ │ AppService.java
│ │
│ └─impl
│ AppServiceImpl.java
│
├─k8s
│ docker-entrypoint.sh # 将容器的启动命令
│ Dockerfile # 用于将jar构建为镜像的Dockerfile
│ gaf-extend-deploy-backend.yaml # 在iManager K8S上部署的GAF,扩充站点,增加微服务的编排文件
│
└─resources
bootstrap-dev.yml # 本地开发环境的配置,包含数据源和mybatis相关配置
bootstrap-prod.yml # 生产环境配置
注意:设置IDE的Line sperator 为 LF,确保文件的行分割符为LF
使用如下命令生成前端工程
npm config set registry https://nexus.gaf.net.cn/repository/npm-group
yarn global add @gaf/create-gaf-project
create-gaf-project extend1
子应用
extend1
extend1
10200
GAF UI 框架
是
extend1
得到如下工程结构
前端基于Vue, Ant Design Vue, GAF-UI,微前端qiankun等技术栈,
在后端IDE中导入后端工程,启动时设置激活的配置文件为dev
然后,在Application类中启动SpringBoot程序
注意1:本地需要提前安装JDK1.8及以上、Maven 3.6.3及以上、配置好后端IDE
在前端工程根目录下,执行如命令
yarn install
yarn serve
注意2:需要提前安装nodejs 16.13.2及以上、安装yarn1.22.5及以上、并配置好yarn和npm的仓库地址为https://nexus.gaf.net.cn/repository/npm-group
接着,http://localhost:10204 查看前端启动情况
在GAF后台界面, 开发运维->组件扩展->代码模板管理,新增默认的模板并上传。http://gaf.net.cn/download/gaf/other-resources/front-back-mvc-vm.zip
在 开发运维->组件扩展->代码生成菜单下,使用刚才上传的模板,生成单表的前后端代码。步骤如下。
在数据库中创建表event
,表示水管巡检维修中的事件,代表维修任务,包含 任务内容、任务类型、详细地址等, 可被分配指派给处理人员。DDL如下。
create table event
(
id varchar(255) not null constraint event_pkey primary key,
code varchar(255),
create_at date,
src_type varchar(255),
type varchar(255),
task_type varchar(255),
task_type_detail varchar(500),
report_by varchar(255),
report_by_phone varchar(255),
deal_by varchar(255),
status varchar(255),
description text,
addr varchar(600),
remark text
);
comment on column event.id is '事件id';
comment on column event.code is '事件编号';
comment on column event.create_at is '创建时间';
comment on column event.src_type is '来源类别';
comment on column event.type is '事件类型';
comment on column event.task_type is '任务类别';
comment on column event.task_type_detail is '任务内容';
comment on column event.report_by is '上报人员';
comment on column event.report_by_phone is '联系电话';
comment on column event.deal_by is '处理人员';
comment on column event.status is '事件状态.未分派,未接单,处理中,待审核,无效,正常完成,超时完成';
comment on column event.description is '问题描述';
comment on column event.addr is '详细地址';
comment on column event.remark is '详细备注';
在代码生成页面,填写数据源
选表event
配置模板参数
配置字段
**注意:**将创建时间的实体类型修改为Date
,表单类型修改为日期时间控件
生成下载后,解压压缩包,将后端代码复制到后端工程对应位置。并且注意将mapper文件夹下的EventMapper.xml移动到后端工程的resources的mapper目录下
将前端代码复制到前端工程对应位置
并修改前端工程routes目录下的index.js,增加前端页面路由路径/event。
最后,重新启动前后端。查看 巡检维修的事件管理列表页面,http://localhost:10204/event。可新增编辑事件
在后端工程打包之前,修改bootstrap-prod.yml中数据源的配置为
spring:
application:
name: extend1
datasource:
driver-class-name: org.postgresql.Driver
password: '123456'
# 修改为
url: jdbc:postgresql://gaf-postgres:5432/gaf
username: admin
并且在GAF部署的数据库gaf中,新增event表,DDL语句查看代码生成
。
后端工程mvn clean package
打包为jar包,前端工程yarn build
,生成dist目录并压缩为dist.zip。
后端上传Dockfile、docker-entrypoint.sh、jar包到GAF文件存储目录 imagebuild/extend1_backend
下
前端上传Dockfile、default.conf、dist.zip包到文件存储目录imagebuild/extend1_frontend
下
在文件存储目录 imagebuild/extend1_backend
下,点击镜像构建
,构建后端镜像,输入镜像的完整名称registry.cn-hangzhou.aliyuncs.com/supermap-gaf/extend1-backend:1.0
。若选择推送,则需要提前准备镜像仓库,然后输入地址及用户名密码。若不推送,则会生成一个tar包,放在文件存储的当前目录下。
同理,在文件存储目录 imagebuild/extend1_frontend
下,点击镜像构建
,构建后端镜像,输入镜像的完整名称registry.cn-hangzhou.aliyuncs.com/supermap-gaf/extend1-frontend:1.0
。若选择推送,则需要提前准备镜像仓库,然后输入地址及用户名密码。若不推送,则会生成一个tar包,放在文件存储的当前目录下。
**注意:**若未使用镜像推送,则可在文件存储
中将生成的tar下载,并上传到GAF的部署环境中,使用docker load -i xxx.tar
,加载镜像用于后续部署。
目前,已经有前后端的镜像,接下来部署并集成到GAF。
若使用单机linux部署GAF,则找到安装包下的docker-compose.yml文件的services下,增加如下内容
extend1-backend:
image: registry.cn-hangzhou.aliyuncs.com/supermap-gaf/extend1-backend:1.0
networks:
- gaf-net
restart: always
container_name: extend1-backend
extend1-frontend:
image: registry.cn-hangzhou.aliyuncs.com/supermap-gaf/extend1-frontend:1.0
networks:
- gaf-net
ports:
- 31333:80
restart: always
container_name: extend1-frontend
然后执行./deploy.sh create extend1-backend
和./deploy.sh create extend1-frontend
,启动前后端。
若基于iManager K8S部署GAF,则分别修改后端工程里面的gaf-extend-deploy-backend.yaml文件中的镜像地址 和 前端工程中的gaf-extend-deploy-frontend.yaml中的镜像地址。 如下
# gaf-extend-deploy-backend.yaml
apiVersion: v1
kind: Service
metadata:
labels:
type: gaf
name: extend1-backend
namespace: ${NAMESPACE}
annotations:
addressReg: "${SERVICE_IP}:${NODE_PORT}"
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
app: extend1-backend
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extend1-backend
namespace: ${NAMESPACE}
labels:
app: extend1-backend
annotations:
scale: unscalable
console: noconsole
portExpose: exposable
description:
topoProperty: "{'name':'extend1-backend','isPivot':'false'}"
spec:
replicas: 1
selector:
matchLabels:
app: extend1-backend
template:
metadata:
labels:
app: extend1-backend
spec:
initContainers:
- name: check-healthy
image: registry.cn-hangzhou.aliyuncs.com/supermap-gaf/busybox-curl:v1.0
command: [ 'sh', '-c', 'until curl -fs http://gaf-microservice-conf:8080/actuator/health; do echo waiting for gaf-microservice-conf; sleep 2; done;' ]
# 修改image为registry.cn-hangzhou.aliyuncs.com/supermap-gaf/extend1-backend:1.0
containers:
- image: registry.cn-hangzhou.aliyuncs.com/supermap-gaf/extend1-backend:1.0
imagePullPolicy: ${IMAGE_PULL_POLICY}
name: extend1-backend
envFrom:
- configMapRef:
name: gaf-env-config
optional: false
dnsPolicy: ClusterFirst
restartPolicy: Always
# gaf-extend-deploy-frontend.yaml
---
apiVersion: v1
kind: Service
metadata:
labels:
type: gaf
name: extend1-frontend
namespace: ${NAMESPACE}
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: extend1-frontend
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
labels:
entrance: "true"
entrance-port: "80"
type: gaf
name: extend1-frontend-nodeport
namespace: ${NAMESPACE}
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
nodePort: 31333
selector:
app: extend1-frontend
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extend1-frontend
namespace: ${NAMESPACE}
labels:
app: extend1-frontend
annotations:
scale: unscalable
console: noconsole
description: 前端扩展组件
topoProperty: "{'name':'extend1-frontend','isPivot':'false'}"
spec:
replicas: 1
selector:
matchLabels:
app: extend1-frontend
template:
metadata:
labels:
app: extend1-frontend
spec:
# 修改为 registry.cn-hangzhou.aliyuncs.com/supermap-gaf/extend1-frontend:1.0
containers:
- image: registry.cn-hangzhou.aliyuncs.com/supermap-gaf/extend1-frontend:1.0
imagePullPolicy: ${IMAGE_PULL_POLICY}
name: extend1-frontend
dnsPolicy: ClusterFirst
restartPolicy: Always
然后在iManger后台站点管理下,选择gaf,点击扩展站点,选择上面修改后的文件。
部署完成后,在GAF后台组件扩展菜单下,后端微服务路由配置添加路由,如下图所示。
前端微应用界面添加路由配置,如下图。注意,微应用入口的ip地址。
在GAF后台界面,系统设置的菜单管理下,新增菜单扩展示例及其子菜单维修事件管理。
最后使用管理员登录查看新增的维修事件菜单。
当扩展的后端工程需要调用其他微服务的接口,可以使用feign来调用。GAF提供了一些jar包,里面包含了一些基于feign的声明式接口调用。例如在后端工程pom.xml中引入依赖
<dependency>
<groupId>com.supermap.gafgroupId>
<artifactId>gaf-sys-mgt-clientartifactId>
<version>3.0.pro-beta-SNAPSHOTversion>
dependency>
在使用的代码处,注入需要使用的对象,例如字典接口客户端
@Service
public class AppServiceImpl implements AppService {
@Autowired
private SysDictClient sysDictClient;
public void getAllDictTreeNode() {
List<DictDataNode> serviceType = sysDictClient.getAllDictDataTreeNode("ServiceType").checkAndGetData();
System.out.println("serviceType = " + serviceType);
}
}
也可以在代码,编写基于feign的声明式接口直接调用GAF的后端接口
由于大部分后端接口逻辑需要用户信息,需要在接口调用链路中传递用户信息。由于在GAF网关处使用jwt存储认证后的用户名和用户id ,存放在请求头Authorization中,传递给下游微服务,当下游微服务之间相互调用时,需要通过请求拦截器传递token。
可通过引入依赖,使用里面已经写好的feign拦截器JWTTokenClientFeignInterceptor
(已经自动注入)和RestTemplate拦截器RestTemplateInterceptor
(需要手动注入)
<dependency>
<groupId>com.supermap.gafgroupId>
<artifactId>gaf-common-rest-feignartifactId>
<version>3.0.pro-beta-SNAPSHOTversion>
dependency>
由模板创建的后端工程中,已经引入了gaf-common-rest-feign依赖。
在后端工程pom.xml中,引入gaf-extend-auth依赖。
<dependency>
<groupId>com.supermap.gafgroupId>
<artifactId>gaf-extend-authartifactId>
<version>3.0.pro-beta-SNAPSHOTversion>
dependency>
在需要用户信息的代码处,注入GafUserService
对象,如下所示
@RestController
public class AppRestController {
@Autowired
private AppService appService;
// 注入GafUserService对象
@Autowired
private GafUserService gafUserService;
// servicePrefix
@RequestMapping("/hello")
public AppBean helloApp(@RequestParam(value = "name", defaultValue = "rest server") String name) {
return appService.helloApp(name);
}
@RequestMapping("/user")
public Map<String, Object> user() {
Map<String, Object> result = new HashMap<>(16);
// 获取用户名
String username = gafUserService.getUserName();
// 获取租户id
String tenantId = gafUserService.getTenantId();
// 获取用户id
String userId = gafUserService.getUserId();
// 获取用户信息
AuthUser authUser = gafUserService.getUserInfo();
result.put("username", username);
result.put("tenantId", tenantId);
result.put("userId", userId);
result.put("authUser", authUser);
return result;
}
}