该项目主要完成了平台的后端工程微服务的搭建和后端接口的实现,以及后期项目调优等工作。
一、微服务部署
微服务模块如下:
其中,需要与前台对接的微服务的主要是 kkb-admin 和 kkb-portal 两个,也是我们需要实现业务的两个模块,很多代码都调用到了 kkb-common 中的工具,如 redis 存取工具等。
kkb-mbg-plus代码生成工具也是神器,自动生成 pojo、dao、service 层内的通用代码。
kkb-gateway 主要是我部署的,内部设置了很多转发、过滤的规则。
这些模块都是我主要负责的模块。
二、数据库设计
数据库设计是根据项目需求设计的,需要用到 PRD(Project Requirement Document,项目需求文档)。其中需要注意的一些点:
- 删除采用逻辑删除,逻辑删除字段类型采用与主键类型一致(自增主键),0表示未被删除,其它表示已被删除。此外,由于逻辑删除的存在,导致主键索引无法过滤掉已被删除的记录,这里可以建立联合索引方便查找“活着”的用户。
- 描述用户种类可以另起一张表,方便日后扩展用户类型;还要另起一张用户与用户种类对应的表,方便查询。
二、后端接口
我主要负责后台名人堂显示用户列表的接口,由于pojo(dto、vo是自己编写的)、dao、service(部分功能也要自己编写)层都是用mbg-plus生成好的,我们主要负责Controller业务逻辑的编写。
-
调用 mbg-plus 服务生成 pojo、dao、service
这里主要是用到了第三方编写的 mybatis plus 生成器来生成相应的实体类、数据库接口类、服务类,源码如下:/** * @Description mybatisPlus配置(生成器) * @Author qtds * @Date 2021/8/11 , 15:31 */ public class KkbMbgPlusGenerator { public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); Properties properties = new Properties(); try { properties.load(KkbMbgPlusGenerator.class.getResourceAsStream("/mybatis-plus.properties")); // 配置文件配置即可 } catch (IOException e) { e.printStackTrace(); } MybatisPlusConfig mybatisPlusConfig = new MybatisPlusConfig(properties); mpg.setDataSource(mybatisPlusConfig.dataSourceConfig()); mpg.setGlobalConfig(mybatisPlusConfig.globalConfig()); mpg.setPackageInfo(mybatisPlusConfig.packageConfig()); mpg.setStrategy(mybatisPlusConfig.strategyConfig()); mpg.execute(); } }
只需要配置 mybatis-plus.properties 文件后运行启动类即可快速配置所需类。
注意:运行前,需要在数据库中对应位置创建数据库和数据表。
mybatis-plus.properties 配置文件如下:#生成文件的输出目录 globalConfig.outputDir=/kkb-admin/src/main/java #数据源配置 url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC driverClassName=com.mysql.cj.jdbc.Driver username=root password= #父包名 package.parent=com.kkb.kkbadmin #父包模块名 package.moduleName= #Entity包名 package.entity=domain #Mapper包名 package.mapper=dao #Controller包名 package.controller=controller package.xml=dao.xml #需要包含的表名,多个表使用逗号隔开,不写代表生成所有表 strategy.include=carousel
-
编写适配业务的 service
业务五花八门,需要的业务往往没有对应的 service 接口能直接用上。
重新设计接口只需要在 service 包中找到对应 interface 接口,在其中添加相应的接口函数,然后在 impl 包找到对应的实现类实现相应方法,就可以供 controller 层调用了。
举例 User 服务层接口 UserService:/** * 用户表 服务类 * * @author 赵裕源 * @since 2021-08-15 */ public interface UserService extends IService<User> { /** * 带条件的分页查询 * * @param condition 条件 * @param pageNum 页码 * @param pageSize 每页显示行数 * @return pageInfo, PageHelper的分页类 */ PageInfo<FameBGListVO> findPage(Long roleId, UserDto condition, Integer pageNum, Integer pageSize); /** * 根据用户Id列表List的条件返回用户列表List * * @param condition 条件 * @param userIds 用户Id列表 * @return 用户列表List */ List<FameBGListVO> selectByConditionAndUserIds(UserDto condition, List<Long> userIds); }
该接口的实现类 UserServiceImpl:
/** * 用户表 服务实现类 * * @author 赵裕源 * @since 2021-08-15 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Autowired private UserRoleService userRoleService; @Autowired private ProjectRegisterService projectRegisterService; /** * 带条件的分页查询 * * @param condition 条件 * @param pageNum 页码 * @param pageSize 每页显示行数 * @return pageInfo, PageHelper的分页类 */ @Override public PageInfo<FameBGListVO> findPage(Long roleId, UserDto condition, Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); List<Long> userIds = userRoleService.findUserIdListByRoleId(roleId); List<FameBGListVO> fameBGListVOS = this.selectByConditionAndUserIds(condition, userIds); return new PageInfo<>(fameBGListVOS); } /** * 根据用户Id列表List的条件返回用户列表List * * @param condition 包含用户名的搜索条件 * @param userIds 用户Id列表 * @return 用户列表List */ @Override public List<FameBGListVO> selectByConditionAndUserIds(UserDto condition, List<Long> userIds) { if (CollUtil.isEmpty(userIds)) Asserts.fail("没有查询到相关用户"); String name = condition.getName(); List<User> users = this.lambdaQuery() .like(ObjectUtil.isNotNull(name), User::getName, name) .in(User::getId, userIds) .list(); // 查找需要返回的用户信息放在vo中返回vo列表 return users.stream().map(user -> { FameBGListVO vo = new FameBGListVO(); Long id = user.getId(); vo.setId(id); vo.setUrl(user.getHeadUrl()); vo.setName(user.getName()); vo.setJob(user.getJob()); // 根据用户id查找已完成的项目数 vo.setProjectNum(projectRegisterService.completedProjects(id)); return vo; }).sorted((vo1, vo2) -> vo2.getProjectNum() - vo1.getProjectNum()).collect(Collectors.toList()); } }
注意的点:
- 这里用到的 vo 类是根据接口需要而设计的用于响应前端请求的实体类。
- 为了避免使用大量的 for 循环来填充 vo 对象,巧用 java 提供的 Stream 流对象来完成填充工作,非常方便。
参考:Stream流操作参考 - 善用 Common 工程中的方法,增加程序的可读性。
如:CollUtil.isEmpty(object)
判断对象是否为空等。 - 这里用到了分页显示的工具 PageHelper,只需要在用到的 Service 接口函数添加分页函数
PageHelper.startPage(pageNum, pageSize);
即可返回 PageInfo 分页对象,也很方便。当然也有其他分页方法可以使用。
-
controller层业务接口编写
这里由于需要生成 Swagger 接口文档,除了需要使用通常 Controller 层需要的注解如@RestController
、@RequestMapping("总映射url")
,还需要使用各种带有 api 字样的注解生成接口文档。另外,还有一些校验注解@Validated
供校验参数使用。具体详见代码:/** * 用户表 前端控制器 * * @author 赵裕源 * @since 2021-08-15 */ @RestController @Api(tags = "用户管理", value = "名人堂后台管理") @RequestMapping("/user") public class UserController { @Autowired private UserService userService; /** * 根据roleId角色编号返回用户列表List * * @param roleId 角色编号 1: 学员 2: 导师 * @param condition 包含用户名的搜索条件 * @param pageNum 页码 * @param pageSize 每页显示数量 * @return 分页结果 */ @ApiOperation("分页查询") @ApiImplicitParams({ @ApiImplicitParam(name = "roleId", value = "用户角色id", dataType = "long"), @ApiImplicitParam(name = "condition", value = "用户筛选条件", dataType = "User"), @ApiImplicitParam(name = "pageNum", value = "当前页数", defaultValue = "1", dataType = "int"), @ApiImplicitParam(name = "pageSize", value = "每页显示数量", defaultValue = "5", dataType = "int") }) @PostMapping("/page/{roleId}") public CommonResult<CommonPage<FameBGListVO>> listPage(@PathVariable Long roleId, @Validated @RequestBody UserDto condition, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "5") Integer pageSize) { PageInfo<FameBGListVO> page = userService.findPage(roleId, condition, pageNum, pageSize); CommonPage<FameBGListVO> commonPage = CommonPage.restPage(page.getList()); return CommonResult.success(commonPage); } }
-
接口调试
编写完 Controller 层业务逻辑代码后,就需要启动 Application 应用程序类来调试,检验接口运行结果。
需要注意的一些点:- 该项目需要开启 nacos 注册中心服务才可正常调试代码,因为配置文件等需要从远程仓库调取,其中都需要注册 nacos 服务。
- 涉及 redis 接口调用时就需要开启 redis 服务,并配置好相应 application.yml 配置文件。
redis 接口函数可以在 common 工程中获取。 - 记得创建好 MySQL 数据库和使用到的数据表,并配置好相应 application.yml 配置文件,才可以调试成功。
三、项目调优
项目调优有三个方向:
-
主从复制+多数据源+Redis 缓存
主从复制操作参考:MySql主从复制,从原理到实践!
多数据源配置则只需要在相应 application.yml 配置文件中配置即可,如下:spring: application: name: kkb-portal autoconfigure: exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源 ######################## # 多数据源关键配置 # ######################## datasource: master: url: jdbc:mysql://192.168.200.128:3307/db2? useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.jdbc.Driver slave: url: jdbc:mysql://192.168.200.128:3308/db2? useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.jdbc.Driver druid: initial-size: 5 #连接池初始化大小 min-idle: 10 #最小空闲连接数 max-active: 20 #最大连接数
Redis 缓存配置参考:MySql主从复制,从原理到实践!
-
ELK:Elasticsearch + Logstash + Kibana 日志系统
这组合是现在大型项目常用的一个日志收集并显示的系统。
Logstash 负责收集日志;
Elasticsearch 负责提供大文件关键字搜索服务,可以定位日志的出处等,方便排查bug;
Kibana 负责显示查找结果,属于前端服务。
部署参考:SpringBoot应用整合ELK实现日志收集 -
Elasticsearch 搜索项目
ElasticSearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful web 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。
部署参考:springboot+elasticsearch实现一个搜索引擎的功能