该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

学习目标

了解九点钟项目 了解九点钟项目技术架构 独立搭建后台管理系统 独立搭建系统微服务 实现全局异常处理

项目介绍

企业自己部署办公项目

  • 组建开发团队 花费百万以上 成本高
  • 租赁服务(Saas模式)

Saas软件即服务,传统方式软件交付必须有聚合安装包,代码。使用Saas服务以后不需要安装软件,也能享受响应服务。本质从买到租模式转变。 核心:将软件从所有权转为使用权。

​ 九点钟移动办公云平台:全新的移动办公云平台,主要帮助企业轻松解决:考勤统计难,排班混乱、特殊工时难以有效管理等。 移动端轻松地实现考勤统计、轻简办公、沟通协作、企业管理,能够极大地节省企业日常考勤、排班、办公、沟通、管理的成本。集知识分享、审批流程、数据协作等应用于一体的移动办公云平台 。每天要做什么、做了什么、有什么收获;实时推进目标成果、构建高效率协作团队。

​ 本项目主要利用当前最为流行的SpringCloud微服务技术架构及SaaS模式,解决很多企业所面临的管理困难。企业办公自动化方面我们抽取通用模块,做成通用平台,可以用SaaS方式解决传统办公自动化软件开发及使用的问题,非常类似近期很火爆的阿里钉钉的一款移动办公产品。涉及众多的实战业务,主要包括的服务模块:1.消息服务 ;2.系统服务;3.企业服务;4.签到服务;5.考勤服务;6文档服务;7.审批服务等。打造以人为本、数据驱动的专业行业解决方案,为各行业提供新工作方式。

​ 致力于企业办公解决方案,业务亮点:移动办公新模式的体验,将移动办公和SaaS模式进行结合,消息服务解决企业内员工的沟通协调工作;可以实现微服务鉴权及细粒度权限控制;完善的审批服务可以更加快速解决流程制订,流程审批等工作;企业服务可以将九点钟项目打造成平台项目,让更多公司入驻到九点钟移动办公云平台;签到服务使用百度地图实现签到人所在位置的地图位置信息精准记录;报表解决方案实现数据整合抽象化提取。

PC端管理平台入口:https://9clock-admin-dev.itheima.net/

项目模块划分

九点钟移动办公从用户角度出发分为两部分:PC端APP端

  1. 企业管理:创建企业/加入企业、企业信息管理;企业管理-组织架构管理;
  2. 工作消息:团队内部在线沟通桥梁。每一次沟通都是围绕工作成果及其事项协作建立互动。支持单聊/群聊,文字/语音、文件/图片传输。
  3. 移动考勤:企业可定制排班,设置各种复杂排班,排班一键搞定。基于地理位置的手机签到签退并结合企业上下班时间,自动形成团队员工的考勤记录。外勤、迟到、早退?第一时间掌握员工的考勤信息。解决传统打卡机,无法考核外出员工的问题,基于行业领先的百度地图定位技术,定位精确,随时掌控外勤人员的实时位置,以及每天的工作轨迹。管理者可直接在手机上为轮班、倒班的员工,灵活的定制考勤方案了,每月统计报表自动生成,更高效、更便捷
  4. 流程审批:各种工作中的业务表单帮助您快速审批,对于初创或快速发展中的企业团体,越少的限制、越灵活的审批环节,随时随地都能申请审批,让公司办事效率大幅提升。
  5. 文档中心:建立企业团队内部知识网盘,积累沉淀工作工程中的知识经验,全员创新、全员分享,充分利用。知识干货/经验分享,实时传递最新资讯,搭建共享学习“生态圈”。
  6. 权限管理:企业管理权限、员工管理审核、编辑权限、打卡设置权限、考勤统计、考勤编辑权限、发布公告权限。
  • PC端(企业管理员)

    • 审批管理:包括审批表单设计(基础设置,表单设计,流程设计),审批管理
    • 企业管理:包括基本信息维护、通讯录信息、变更企业管理员,企业组织架构
    • 文档中心:包括文档新增、修改、分享等
    • 考勤管理:包括考勤组管理,加班规则设置,补卡规则设置,假期设置
    • 签到管理:签到统计,签到数据导出
  • APP端:(企业员工)

    • 通用:包括用户注册、用户登陆、企业注册、加入企业申请,找回密码
    • 消息:包括工作通知、团队申请消息、聊天
    • 审批:包括我的审批,审批详情(回退,会签,知会,加签,减签),审批转交,审批同意/拒绝
    • 考勤:考勤统计,打卡,申请补卡
    • 签到:签到提交,签到统计,签到详情,签到历史分布
    • 文档:文档新建,文档编辑,文档分享
    • 联系人:创建团队,组织架构,联系人列表,联系人详情
    • 通讯录:显示团队员工列表,及时通讯(IM)

课程技术架构

微服务是一种架构方式,最终肯定需要技术架构去实施。

微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?

  • 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。

  • 技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了

  • 群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。

  • 使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建

  • 后端技术:

    • 基础的SpringMVC、Spring 5.0和MyBatis3/mybatis-plus
    • SpringBoot2.1.7/Spring Cloud Greenwich.Release(Consul,Gateway,OpenFeign,Hystrix,Ribbon...)
    • Redis-4.0
    • SpringSecurity/Oauth2/JWT
    • RocketMQ-4.5.2
    • activity5+open workflow
    • poi
    • xxl-job
    • 环信消息(及时通讯)
    • 极光推送(移动端消息推送)
    • MongoDB

九点钟移动办公架构缩略图,大图请参考课前资料:

1571398348422

接口文档

​ 移动互联网时代,你还在用Word管理接口文档吗?你还在使用拼接URL测试吗?你OUT了!

​ YApi 是高效易用功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API,YApi 还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。

访问地址

YApi特性

  • 基于 Json 和 Mockjs 定义接口返回数据的结构和文档,效率提升多倍

  • 扁平化权限设计,即保证了大型企业级项目的管理,又保证了易用性

  • 类似 postman 的接口调试

  • 自动化测试, 支持对 Response 断言

  • MockServer 除支持普通的随机 mock 外,还增加了 Mock 期望功能,根据设置的请求过滤规则,返回期望数据

  • 支持 postman, har, swagger 数据导入

  • 免费开源,内网部署,信息再也不怕泄露了

1571469933365

https://www.taojibao.cn/project/468/interface/api

登录账号:bj144@qq.com java123

数据库设计

系统模块:nc_sys

表名称 含义
company_user 企业员工表
sys_role 角色表
sys_function 菜单/权限
common_industry 行业字典表
表名称 含义
company 企业信息表
department 部门表
address_book_config 企业通讯录字段表

文档模块:nc_doc

表名称 含义
file_history 文件操作历史表
doc_folder 文件夹表
doc_file 文件表
collaborations 文件协同表

考勤模块:nc_attendance

表名称 含义
company_common_setting 公司通用设定
attend_group 考勤组信息表
attend_group_part 考勤人员详情表
overtime_rule 加班规则表
overtime_rule_detail 加班规则详情表
holiday_rule 假期规则表
holiday_rule_age 假期规则和司龄关联表
atte_pubch 打卡信息表

签到模块:nc_sign

表名称 含义
sign 签到表
sign_picture 签到图片资源表

消息模块:存入MongDB

表名称 含义
notifyMessage 消息基础表

审批模块:nc_approve

表名称 含义
approve_definition_template 审批定义模板表
approve_definition 审批定义表
approve_inst 审批实例表
approve_inst_node 审批实例节点表
approve_inst_record 审批实例记录表

系统框架搭建

技术选型

后端技术:

  • 基础的SpringMVC、Spring 5.0和MyBatis3/mybatis-plus
  • SpringBoot2.1.7/Spring Cloud Greenwich.Release
  • Redis-4.0
  • SpringSecurity/Oauth2/JWT
  • RocketMQ-4.5.2
  • nginx-1.12.2
  • activity5+open workflow
  • poi
  • xxl-job
  • 环信消息

开发环境

为了保证开发环境的统一,希望每个人都按照我的环境来配置:

  • IDE:我们使用Idea 2019.1.3 版本
  • JDK:统一使用JDK1.8
  • 项目构建:maven3.3.x以上版本即可
  • MYSQL数据:5.7

idea大家可以在我的课前资料中找到。另外,使用帮助大家可以参考课前资料的《idea使用指南.md》

访问域名

我们在开发的过程中,为了保证以后的生产、测试环境统一。尽量都采用域名来访问项目。

一级域名:www.nineclock.com(PC端访问地址)

二级域名:api.nineclock.com(网关访问地址)

我们可以通过switchhost工具来修改自己的host对应的地址,只要把这些域名指向127.0.0.1,那么跟你用localhost的效果是完全一样的。

switchhost可以去课前资料寻找。

image-20210314095002268

创建父工程

创建Maven工程(pom)nc_parent,用来管理依赖及其版本,注意是创建project,而不是moudle。

删除src目录

在pom.xml中引入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>nine_clock_parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <description>黑马程序员 - 九点钟项目</description>
    <packaging>pom</packaging>

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/>
    </parent>

    <!--定义版本-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
        <mybatis.plus.starter.version>3.1.1</mybatis.plus.starter.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis.plus.starter.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
</project>

注册中心

Spring Cloud Consul

​ Consul 是 HashiCorp 公司推出的开源工具,采用go语言开发,go语言高并发有优势。用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”(consul=eureka+springcloudConfig),内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等)。使用起来也较为简单。

​ Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。

Consul特点:

  • 服务发现
  • 健康检查
  • Key/Value 存储
  • 多数据中心

Consul核心:agent组件

​ Agent是一个独立的程序,通过守护进程的方式,运行在consul集群中的每个节点上。每个Consul agent维护它自己的服务集合以及检查注册和健康信息。agent负责执行自己的健康检查和更新本地状态其中,Agent 根据节点的性质,分为: Agent Server 和 Agent Client

  • Agent Client:

client将 HTTP 和 DNS 接口请求转发给局域网内的服务端集群。

  • Agent Server:

server 保存client的注册信息,集群的配置信息, 维护集群高可用, 在局域网内与本地客户端通讯, 通过广域网与其它数据中心通讯。 每个数据中心的 server 数量推荐为 3 个或是 5 个,通过 Raft 算法来保证一致性。

SpringCloudConsul工作原理:

1571907590100

Consul对比Eureka

最大区别:C(一致性)A(可用性)P(分区容错性)原则 Eureka保证AP Consul保证CP

Consul强一致性带来: 保证强一致性但牺牲可用性

  • 服务注册相比Eureka要慢一些,因为Consul的raft协议要求在consul集群环境中必须超过半数的节点都写入成功了,才认为某个微服务注册成功。、
  • 当Leader宕机,重新选举期间整个Consul不可用

Eureka:保证高可用(A)和最终一致性: 保证可用性牺牲一致性

  • 服务注册要比Consul快,因为不需要等待其他节点同步注册信息成功
  • 当数据不一致,虽然AB注册信息不完全相同。但是每个Eureka节点依然可以对外提供服务。出现现象:查询服务信如果请求A查询不到,请求B查询到数据。

Eureka就是Servlet程序,运行在Serlvet容器中。 consul采用是GO语言编写

Feature Consul euerka
服务健康检查 服务状态,内存,硬盘等 可配支持
多数据中心 支持
kv 存储服务 支持
一致性 raft
cap cp ap
使用接口(多语言能力) 支持 http 和 dns http(sidecar)
watch 支持(集群监控) 全量/支持long polling 支持 long polling/大部分增量
自身监控 metrics metrics
spring cloud 集成 已支持 已支持

Consul安装(单机版本)

window下安装

​ Consul 不同于 Eureka 需要单独部署项目安装,访问Consul 官网(https://consul.io/)下载 Consul 的最新版本,我这里是 consul_1.5.3。根据不同的系统类型选择不同的安装包,从下图也可以看出 Consul 支持所有主流系统。附下载地址:https://www.consul.io/downloads.html

1571496913522

我这里以 Windows 为例,下载下来是一个 consul_1.2.1_windows_amd64.zip 的压缩包,解压是是一个 consul.exe 的执行文件。

1571497226838

打开命令行,cd 到对应的目录下,使用 cmd 执行命令consul agent -dev:启动 Consul

consul agent -dev -client=0.0.0.0       # -dev表示开发模式运行,另外还有-server -client表示服务模式运行

1571497437337

为了方便期间,可以在同级目录下创建一个 consul-start.bat 脚本来启动,脚本内容如下:

consul agent -dev
pause

只需要双击脚本文件即可启动consul服务。

1571497711205

启动服务后,通过浏览器访问:http://localhost:8500,可以看到 Consul 的管理界面,说明consul服务已经成功启动了。

1571497791130

基本概念:

  • agent 组成 consul 集群的每个成员上都要运行一个 agent,可以通过 consul agent 命令来启动。agent 可以运行在 server 状态或者 client 状态。自然的,运行在 server 状态的节点被称为 server 节点;运行在 client 状态的节点被称为 client 节点。
  • client consul的client模式,就是客户端模式。是consul节点的一种模式,这种模式下,所有注册到当前节点的服务会被转发到server,本身是不持久化这些信息。
  • server 表示consul的server模式,表明这个consul是个server,这种模式下,功能和client都一样,唯一不同的是,它会把所有的信息持久化的本地,这样遇到故障,信息是可以被保留的。

启动参数说明:

  • bootstrap-expect 集群期望的节点数,只有节点数量达到这个值才会选举leader
  • server 运行在server模式
  • data-dir 指定数据目录,其他的节点对于这个目录必须有读的权限
  • node 指定节点的名称
  • bind 为该节点绑定一个地址
  • config-dir 指定配置文件,定义服务的,默认所有一.json结尾的文件都会读
  • enable-script-checks=true 设置检查服务为可用
  • datacenter 数据中心名称,
  • join 加入到已有的集群中
  • ui 使用自带的ui
  • client 指定web ui、的监听地址,默认127.0.0.1只能本机访问,改为0.0.0.0可外网访问
consul agent -server -bind=127.0.0.1 -client=0.0.0.0 -bootstrap-expect=3 -data-dir=H:\run_software\consul\data1 -node=server1

Consul安装(集群版本)

1571658359035

接下来我们来实现在一台服务器上安装三个server,一个client方式搭建集群环境来完成演示。consul 的搭建方式可以通过命令行方式也可以通过配置文件方式实现。

  1. 创建config1,data1,config2,data2,config3,data3,config4,data4目录,分别对应每个节点的配置文件和数据文件存放目录。

    1571916515993

  2. config1 目录下配置文件名 cluster.json 文件内容如下:

    {
       "data_dir": "H:\\run_software\\consul\\data1",
       "node_name": "server1",
       "server": true,
       "bootstrap_expect": 3,
       "bootstrap": false,
       "datacenter": "aws",
       "advertise_addr": "127.0.0.1",
       "bind_addr": "127.0.0.1",
       "log_level": "INFO",
       "enable_syslog": false,
       "ports": {  
        "http": 8500 ,
        "https": 8501,
        "dns": 8600,
        "grpc": 8502,
        "serf_lan": 8301,
        "serf_wan": 8302, 
        "server": 8300
          },
        "disable_host_node_id":true,
        "retry_join": ["127.0.0.1:8301","127.0.0.1:7302","127.0.0.1:6303"]
    }
    
  3. config2 目录下配置文件名 cluster.json 文件内容如下:

    {
    	"data_dir": "H:\\run_software\\consul\\data2",
    	"node_name": "server2",
    	"server": true,
    	"bootstrap_expect": 3,
    	"bootstrap": false,
    	"datacenter": "aws",
    	"advertise_addr": "127.0.0.1",
    	"bind_addr": "127.0.0.1",
    	"log_level": "INFO",
    	"enable_syslog": false,
    	"ports": {
    		"http": 7501,
    		"https": 7502,
    		"dns": 7601,
    		"grpc": 7503,
    		"serf_lan": 7302,
    		"serf_wan": 7303,
    		"server": 7301
    	},
    	"disable_host_node_id": true,
    	"retry_join": ["127.0.0.1:8301", "127.0.0.1:7302", "127.0.0.1:6303"]
    }
    
  4. config3 目录下配置文件名 cluster.json 文件内容如下:

    {
    	"data_dir": "H:\\run_software\\consul\\data3",
    	"node_name": "server3",
    	"server": true,
    	"bootstrap_expect": 3,
    	"bootstrap": false,
    	"datacenter": "aws",
    	"advertise_addr": "127.0.0.1",
    	"bind_addr": "127.0.0.1",
    	"log_level": "INFO",
    	"enable_syslog": false,
    	"ports": {
    		"http": 6502,
    		"https": 6503,
    		"dns": 6602,
    		"grpc": 6504,
    		"serf_lan": 6303,
    		"serf_wan": 6304,
    		"server": 6302
    	},
    	"disable_host_node_id": true,
    	"retry_join": ["127.0.0.1:8301", "127.0.0.1:7302", "127.0.0.1:6303"]
    }
    
  5. config4 目录下配置文件名 cluster.json 文件内容如下:

    {
    	"data_dir": "D:\\run_software\\consul\\data4",
    	"node_name": "client-node",
    	"server": false,
    	"bootstrap": false,
    	"datacenter": "aws",
    	"advertise_addr": "127.0.0.1",
    	"bind_addr": "127.0.0.1",
    	"log_level": "INFO",
    	"enable_syslog": false,
    	"ui": true,
    	"ports": {
    		"http": 5503,
    		"https": 5504,
    		"dns": 5603,
    		"grpc": 5505,
    		"serf_lan": 5304,
    		"serf_wan": 5305,
    		"server": 5303
    	},
    	"disable_host_node_id": true,
    	"start_join": ["127.0.0.1:8301", "127.0.0.1:7302", "127.0.0.1:6303"]
    }
    
  6. consul 解压后的目录,然后依次启动server1,server2,server3,client1 节点,命令顺序如下:

    consul agent -dev -client=0.0.0.0
    

    1571648738245

    1571648480764

    1571648502686

    1571648522422

  7. 启动完后,通过如下命令查看集群状态。

    1571648825670

  8. 在浏览中中输入地址http://127.0.0.1:5503/ui访问查看集群结果

    1571648900649

创建Gateway网关

​ 网关微服务也需要注册到Consul中,网关微服务要从Consul中拉取其他微服务的注册信息,所以注意在启动网关微服务前需要将Consul注册中心服务启动!

创建工程

选择maven方式创建Module,然后填写项目名称,我们命名为:nine_clock_gateway

引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

启动类

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Author: 黑马程序员
 * @description:
 **/
@SpringBootApplication
@EnableDiscoveryClient
public class NcGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(NcGatewayApplication.class, args);
    }
}

配置文件

server:
  port: 10010
spring:
  application:
    name: gateway-service
    #注册中心 相关配置
  cloud:
    consul:
      host: localhost #指定注册中心IP地址
      port: 8500 #指定注册中心端口,默认为8500
      discovery:
        serviceName: gateway-service
        locator:
          lower-case-service-id: true
          enabled: true
        register: true
        prefer-ip-address: true #这个必须配
        tags: version=1.0.0 #写当前项目 开发版本1.1
        instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
        healthCheckInterval: 15s
        health-check-url: http://${spring.cloud.client.ip-address}:${server.port}/actuator/health
    #网关配置
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        #系统服务
        - id: sys-service  #路由名称 该名称任意写
          uri: lb://sys-service #lb:load balacne 负载均衡
          predicates:
            - Path=/sys/**  #匹配路径规则  地址中包含/sys 请求会转发到系统微服务
          filters:
            - StripPrefix=1 #转发请求到目标微服务,1代表去除一位前缀  /** 会被转发到其他微服务
            # localhost:10010/sys/user      /user这部分转发目标微服务
        # 认证中心
        - id: auth-service
          uri: lb://auth-service
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1

项目结构

目前,nc_parent下有一个子模块:

  • nc_gateway:微服务网关(Gateway)

目前,服务的结构如图所示:

启动网关微服务测试:

1571557420463

访问注册中心,查看是否注册成功:

1571557248126

创建通用模块

有些工具或通用的约定内容,我们希望各个服务模块共享,因此需要创建一个工具模块:nc_common

创建工程

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>nineclock</artifactId>
        <groupId>com.itheima</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>nineclock_common</artifactId>

       <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.gavaghan</groupId>
            <artifactId>geodesy</artifactId>
            <version>1.1.3</version>
        </dependency>
        <!-- mapstruct-->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>1.2.0.CR1</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.2.0.CR1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--异常-->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.21</version>
        </dependency>
        <!-- 增加hutool工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.3.9</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.4.2</version>
        </dependency>
    </dependencies>
</project>

引入工具类

每个工具类的作用:

  • BeanHelper:实现Bean属性的拷贝,把一个Bean的属性拷贝到另一个Bean,前提是其属性名一致或部分一致
  • IdWorker:分布式ID生成器
  • JsonUtil:实现实体类与Json的转换
  • DateUtil:实现日期格式化
image-20210305202815139

通用返回结果

package com.itheima.common.vo;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

/**
 * @program: 黑马程序员
 * @description: 服务端提供前端返回的结果
 **/
@Data
public class Result<T> implements Serializable {

    private Boolean success; //是否成功
    private int code;        //返回状态码
    private String message;  //提示消息
    private T data;          //返回数据

    public Result() {
    }

    public Result(Boolean success, int code, String message) {
        this.success = success;
        this.code = code;
        this.message = message;
    }

    public Result(Boolean success, int code, String message, T data) {
        this.success = success;
        this.code = code;
        this.message = message;
        this.data = data;
    }
}

7. 创建系统微服务

我们先完成项目的搭建:首先在mysql中创建系统微服务对应数据库:nc_sys导入对应系统微服务sql脚本

image-20210305204308898

微服务的工程结构

我们的工程命名为nc_sys.

需要注意的是,我们的nc_sys是一个微服务,那么将来肯定会有其它系统需要来调用服务中提供的接口,因此肯定也会使用到接口中关联的实体类。

因此这里我们需要使用聚合工程,将要提供的接口及相关实体类放到独立子工程中,以后别人引用的时候,只需要知道坐标即可。

我们会在nc_sys中创建三个子工程:

  • nc_sys_dto:主要是相关实体类(PO,DTO)
  • nc_sys_service:所有业务逻辑及内部使用接口(controller,service,dao)
  • nc_sys_interface:对外提供接口(对外提供远程调用API接口-FeignClient)

调用关系如图所示:

1571561538127

7.2 创建父工程nc_sys

创建父工程module,打包方式为pom

不需要任何依赖,我们可以把项目打包方式设置为pom,也可将src目录删除。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>nc_parent</artifactId>
        <groupId>com.itheima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>nc_sys</artifactId>
    <!--打包方式改为pom-->
    <packaging>pom</packaging>

</project>

7.3 创建子模块nc_sys_pojo

在nc_sys工程上点击右键,选择new > module:

7.4 创建子模块nc_sys_service

nc_sys_pojo类似,我们选择在nc_sys上右键,新建module,然后填写项目信息:

由于将来我们的业务模块一定会返回数据给前端/移动端,而返回的数据对象都为DTO数据传输对象,所以要在nc_sys_service的pom文件中增加nc_sys_pojo的依赖坐标信息。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>nc_sys</artifactId>
        <groupId>com.itheima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>nc_sys_service</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>nc_sys_pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

7.5 创建子模块nc_sys_interface

与上面类似,我们选择在nc_sys上右键,新建module,然后填写项目信息:

同理对外提供的接口模块也需要引入nc_sys_pojo的依赖坐标信息。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>nc_sys</artifactId>
        <groupId>com.itheima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>nc_sys_interface</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>nc_sys_pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

7.6 完成的目录结构

我们打开nc_sys的pom查看,会发现nc_sys_pojo和nc_sys_service以及nc_sys_interface都已经成为module了:

7.7 编写启动类及配置

7.7.1 增加依赖

接下来我们给nc_sys_service中添加依赖:

思考一下我们需要什么?

  • consul发现客户端
  • web启动器
  • mybatis plus启动器
  • 连接池,我们用默认的Hykira,引入jdbc启动器
  • mysql驱动
  • 公共的子模块nc_common
  • 千万不能忘了,我们自己也需要nc_sys_pojo中的实体类
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>nc_sys</artifactId>
        <groupId>com.itheima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>nc_sys_service</artifactId>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- mybatisplus与springboot整合 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>nc_sys_pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>nc_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

7.7.2.编写配置及启动类

启动类:

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Author: 黑马程序员
 * @description:
 **/
@SpringBootApplication
@EnableDiscoveryClient
public class NcSysApplication {
    public static void main(String[] args) {
        SpringApplication.run(NcSysApplication.class, args);
    }
}

配置文件:

server:
  port: 8081
spring:
  application:
    name: sys-service
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/nc_sys?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root
  cloud:
    consul:
      port: 8500 #指定注册中心端口,默认为8500
      host: 127.0.0.1 #指定注册中心IP地址
      discovery:
        serviceName: sys-service
        locator:
          lower-case-service-id: true
          enabled: true
        register: true
        prefer-ip-address: true #这个必须配
        tags: version=1.2
        instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}
        healthCheckInterval: 15s
        health-check-url: http://${spring.cloud.client.ip-address}:${server.port}/actuator/health
mybatis-plus:
  global-config:
    id-type: 0
  mapper-locations: classpath:/mapper/*.xml
  type-aliases-package: com.itheima.sys.entity
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    com.itheima: debug

Mybatis配置类:

package com.leyou.sys.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    /*** * plus 的性能优化 * 生产环境不适用 * @return */
    @Bean
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); /**/
        performanceInterceptor.setMaxTime(1000); /**/
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    }

    /**
     * @Description : mybatis-plus分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

7.7.3.网关中添加系统微服务路由规则

打开nine_clock_gataway微服务配置文件增加以下内容

spring:
  cloud:
    gateway:
      routes:
        # 系统微服务
        - id: sys-service #微服务名称
          uri: lb://sys-service #即sys-service服务的负载均衡地址
          predicates:     #predicates用于匹配HTTP请求的不同属性
            - Path=/sys/** #匹配到的URL地址
          filters:
            - StripPrefix=1 #在转发之前将/sys 去掉

测试

为了快速测试,我们在nc_sys_service项中提供controller,新增restApi方法进行测试:

package com.itheima.sys.controller;

import com.itheima.common.vo.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/test")
    public Result<String> hello(){
        return new Result<>(true, 200, "请求成功!", "hello nineclock!");
    }
}

由于在网关中增加了路由规则,故测试之前需要将网关微服务以及系统微服务重启访问测试:

通过系统微服务访问测试:

通过网关访问测试:

8. 通用异常处理

8.1 场景预设

我们预设这样一个场景,假如我们做新增用户,需要接收下面的参数:

name:名称
age:年龄

然后对数据做简单校验:

  • 年龄不能为空

需求:新增时,自动生成ID,然后随用户对象一起返回

8.1.1 代码

在nc_sys_pojo中增加用户实体类:

package com.itheima.sys.entity;

import lombok.Data;

@Data
public class UserDomain {

    private Long id;
    private String name;
    private Integer age;
}

在nc_sys_service中增加用户业务类:

package com.itheima.sys.service;

import com.itheima.sys.entity.UserDomain;
import org.springframework.stereotype.Service;

import java.util.Random;

@Service
public class UserDominService {


    /**
     * 模拟测试保存用户记录
     * @param user
     * @return
     */
    public UserDomain saveUser(UserDomain user) {
        Random random = new Random(1000);
        user.setId(random.nextLong());
        return user;
    }
}

在nc_sys_service中增加用户控制器类:

package com.itheima.sys.controller;

import com.itheima.common.vo.Result;
import com.itheima.sys.entity.UserDomain;
import com.itheima.sys.service.UserDominService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @Autowired
    private UserDominService userDominService;

    @GetMapping("/test")
    public Result<String> hello(){
        return new Result<>(true, 200, "请求成功!", "hello nineclock!");
    }

    @PostMapping("/test/user")
    public Result saveUser(@RequestBody UserDomain user){
        //如果年龄为空,则抛出异常,返回400状态码,返回错误提示消息
        if (user.getAge() == null) {
            throw new RuntimeException("用户年龄为必填项!");
        }
        return new Result(true, 200, "保存成功", userDominService.saveUser(user));
    }
}

8.1.2 测试异常

使用POSTMAN发送请求进行测试,注意提交参数格式为Json.

没有提交年龄返回的结果为:

8.1.3 问题分析

​ 刚才的处理看似完美,但是仔细想想,我们在异常处理中,如果抛出RuntimeException交给框架处理返回状态码为固定的500了,仅凭用户抛出的异常,我们根本无法判断到底该返回怎样的状态码,可能是参数有误、数据库异常、没有权限,等等各种情况。

​ 解决问题的思路为,所有的异常全部抛出,然后统一的拦截到异常处理,给前端返回结果。

8.2 统一异常处理

​ 接下来,我们使用SpringMVC提供的统一异常拦截器,因为是统一处理,我们放到nc_common项目中:

新建一个类,名为:BasicExceptionAdvice

具体代码如下:

package com.itheima.common.exception.advice;

import com.itheima.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @Author: 黑马程序员
 * @description:
 **/
@Slf4j
@ResponseBody
@ControllerAdvice
public class BasicExceptionAdvice {

    @ExceptionHandler(RuntimeException.class)
    public Result handleException(RuntimeException e) {
        // 我们暂定返回状态码为400, 然后从异常中获取友好提示信息
        return new Result(false, 400, e.getMessage());
    }

}

解读:

  • @ControllerAdvice:默认情况下,会拦截所有加了@Controller的类

    1534203615380

  • @ExceptionHandler(RuntimeException.class):作用在方法上,声明要处理的异常类型,可以有多个,这里指定的是RuntimeException。被声明的方法可以看做是一个SpringMVC的Handler

    • 参数是要处理的异常,类型必须要匹配
    • 返回结果可以是ModelAndViewResponseEntity等,基本与handler类似
  • 这里等于从新定义了返回结果,我们可以随意指定想要的返回类型。此处使用了String此处使用了spring的注解,因此需要在nc_common中引入web依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
</dependency>

要想在系统服务中扫描到这个advice,需要在nc_sys_service中引入nc_common依赖:

<dependency>
    <groupId>com.itheima</groupId>
    <artifactId>nc_common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

重启系统微服务项目进行测试:

1571585018879

成功返回了错误信息!

8.2.1 异常信息及状态

刚才的处理看似完美,但是仔细想想,我们在通用的异常处理中,把返回状态码写死为400了:

1571585102405

这样显然不太合理。

因此,用户抛出异常时,就必须传递两个内容:

  • 异常信息
  • 异常状态码

但是RuntimeException是无法接受状态码的,只能接受异常的消息,所以我们需要做两件事情:

  • 自定义异常,来接受状态码、异常消息
  • 状态码与异常消息可能会重复使用,我们通过枚举来把这些信息变为常量

8.2.2 自定义异常枚举类型

枚举:把一件事情的所有可能性列举出来。在计算机中,枚举也可以叫多例,单例是多例的一种情况 。

单例:一个类只能有一个实例。

多例:一个类只能有有限个数的实例。

单例的实现:

  • 私有化构造函数
  • 在成员变量中初始化本类对象
  • 对外提供静态方法,访问这个对象

我们定义一个枚举,用于封装异常状态码和异常信息:

1571585362807

package com.itheima.common.enums;

import lombok.Getter;

@Getter
public enum ResponseEnum {

    //一定要将选择项放在最上
    AGE_NOT_NULL(400, "年龄不能为空!"),
    SUCCESS(200, "操作成功!"),
    ERROR(500, "操作失败!"),
    ;

    private Integer code;
    private String message;

    //提供私有无参构造
    ResponseEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}


8.2.3 自定义异常

然后自定义异常,来获取枚举对象。

在nc_common中定义自定义异常类:

1571585701602

package com.itheima.common.exception;

import com.itheima.common.exception.enums.ResponseEnum;
import io.swagger.models.auth.In;
import lombok.Data;

@Data
public class NcException extends RuntimeException {

    private Integer status;

    public NcException(Integer status) {
        this.status = status;
    }

    public NcException(ResponseEnum enums){
        super(enums.getMessage());
        this.status = enums.getCode();
    }

    public NcException(Integer status, String message) {
        super(message);
        this.status = status;
    }

    public NcException(Integer status, String message, Throwable cause) {
        super(message, cause);
        this.status = status;
    }
}

修改Controller中代码:

package com.itheima.sys.controller;

import com.itheima.common.enums.ResponseEnum;
import com.itheima.common.exception.NcException;
import com.itheima.common.vo.Result;
import com.itheima.sys.entity.UserDomain;
import com.itheima.sys.service.UserDominService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @Autowired
    private UserDominService userDominService;

    @GetMapping("/test")
    public Result<String> hello(){
        return new Result<>(true, 200, "请求成功!", "hello nineclock!");
    }

    @PostMapping("/test/user")
    public Result saveUser(@RequestBody UserDomain user){
        //如果年龄为空,则抛出异常,返回400状态码,返回错误提示消息
        if (user.getAge() == null) {
            //throw new RuntimeException("用户年龄为必填项!");
            throw new NcException(ResponseEnum.AGE_NOT_NULL);
        }
        return new Result(true, 200, "保存成功", userDominService.saveUser(user));
    }
}

修改全局异常处理逻辑:

package com.itheima.common.exception;

import com.itheima.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author: 黑马程序员
 * @description:
 **/
@Slf4j
@ResponseBody
@ControllerAdvice
public class BasicExceptionAdvice {

    @ExceptionHandler(RuntimeException.class)
    public Result handleException(RuntimeException e) {
        // 我们暂定返回状态码为500, 然后从异常中获取友好提示信息
        return new Result(false, 400, e.getMessage());
    }

    /**
     * 处理自定义异常
     * @return
     */
    @ExceptionHandler(NcException.class)
    public Result handlerNcException(NcException e){
        return new Result(false, e.getStatus(), e.getMessage());
    }

    /**
     * 处理其他异常
     * @param exception
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Result handlerNcException(Exception e){
        return new Result(false, 500, e.getMessage());
    }

}

测试结果如下:

1571586032651

8.2.4 封装返回结果

将来返回结果给前端,不管成功还是失败都需要返回结果对象Result,目前每次返回都必修手动New一个对象,使用起来比较繁琐。改为将成功,以及失败的Result对象返回的静态方法封装在Result类中。

package com.itheima.common.vo;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.itheima.common.exception.enums.ResponseEnum;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 统一给客户端响应结果对象
 */
@Data
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)  //转json条件
public class Result<T> implements Serializable {

    //是否执行成功
    private Boolean success;

    //响应业务状态码
    private Integer code;

    //提示信息
    private String message;

    //数据
    private T data;

    public Result(Boolean success, int code, String message) {
        this.success = success;
        this.code = code;
        this.message = message;
    }

    public Result(Boolean success, int code) {
        this.success = success;
        this.code = code;
    }

    public Result(Boolean success, int code, T data) {
        this.success = success;
        this.code = code;
        this.data = data;
    }

    public Result(Boolean success, int code, String message, T data) {
        this.success = success;
        this.code = code;
        this.message = message;
        this.data = data;
    }



    //TODO 问题 响应结果在controller方法中需要每次都new 解决:抽取静态方法代表成功 或者 失败结果

    public static <T> Result<T> success() {
        return new Result<T>(true, ResponseEnum.SUCCESS.getStatus());
    }

    public static <T> Result<T> successMessage(String message) {
        return new Result<T>(true, ResponseEnum.SUCCESS.getStatus(), message);
    }

    public static <T> Result<T> success(T data) {
        return new Result<T>(true, ResponseEnum.SUCCESS.getStatus(), data);
    }

    public static <T> Result<T> success(String message, T data) {
        return new Result<T>(true, ResponseEnum.SUCCESS.getStatus(), message, data);
    }

    public static <T> Result<T> error() {
        return new Result<T>(false, ResponseEnum.ERROR.getStatus(), ResponseEnum.ERROR.getMessage());
    }

    public static <T> Result<T> errorMessage(String errorMessage) {
        return new Result<T>(false, ResponseEnum.ERROR.getStatus(), errorMessage);
    }

    public static <T> Result<T> errorCodeMessage(int errorCode, String errorMessage) {
        return new Result<T>(false, errorCode, errorMessage);
    }
}

修改项目中正常以及异常处理方法返回结果:

package com.itheima.common.exception;

import com.itheima.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author: 黑马程序员
 * @description:
 **/
@Slf4j
@ResponseBody
@ControllerAdvice
public class BasicExceptionAdvice {

    @ExceptionHandler(RuntimeException.class)
    public Result handleException(RuntimeException e) {
        // 我们暂定返回状态码为400, 然后从异常中获取友好提示信息
        //return new Result(false, 500, e.getMessage());
        return Result.errorCodeMessage(500, e.getMessage());
    }

    /**
     * 处理自定义异常
     * @return
     */
    @ExceptionHandler(NcException.class)
    public Result handlerNcException(NcException e){
        //return new Result(false, exception.getStatus(), exception.getMessage());
        return Result.errorCodeMessage(e.getStatus(), e.getMessage());
    }

    /**
     * 处理其他异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Result handlerNcException(Exception e){
        //return new Result(false, 500, e.getMessage());
        return Result.errorCodeMessage(500, e.getMessage());
    }

}

再次修改controller中代码:

8.3 返回异常枚举类

如果还有其他异常提示信息,只需要增加即可!

package com.itheima.common.enums;

import io.swagger.models.auth.In;

/**
 * 异常结果枚举类型
 * 本质上封装 业务状态码 提示消息
 */
public enum ResponseEnum {
    //提供每一种提示消息类别
    OK(200, "请求成功!"),
    SUCCESS(200, "操作成功!"),
    ERROR(500, "请求失败!"),
    AGE_NOT_NULL(400, "年龄必须填写!!"),

    INVALID_FILE_TYPE(400, "无效的文件类型!"),
    INVALID_PARAM_ERROR(400, "无效的请求参数!"),
    INVALID_PHONE_NUMBER(400, "无效的手机号码"),
    INVALID_VERIFY_CODE(400, "验证码错误!"),
    INVALID_USERNAME_PASSWORD(400, "无效的用户名和密码!"),
    INVALID_SERVER_ID_SECRET(400, "无效的服务id和密钥!"),
    INVALID_NOTIFY_PARAM(400, "回调参数有误!"),
    INVALID_NOTIFY_SIGN(400, "回调签名有误!"),

    DATA_TRANSFER_ERROR(500, "数据转换异常!"),
    INSERT_OPERATION_FAIL(500, "新增操作失败!"),
    UPDATE_OPERATION_FAIL(500, "更新操作失败!"),
    DELETE_OPERATION_FAIL(500, "删除操作失败!"),
    FILE_UPLOAD_ERROR(500, "文件上传失败!"),
    DIRECTORY_WRITER_ERROR(500, "目录写入失败!"),
    FILE_WRITER_ERROR(500, "文件写入失败!"),
    SEND_MESSAGE_ERROR(500, "短信发送失败!"),
    CODE_IMAGE_ERROR(500, "验证码错误!"),

    USER_MOBILE_EXISTS(500, "该手机号已经被注册!"),
    USER_NOT_REGISTER(500, "当前用户还未进行注册!"),
    USER_NOT_JOIN_COMPANY(500, "请加入企业后再使用该功能!"),
    USER_NOT_COMPANY_ADMIN(500, "您不是企业管理员!"),
    USER_NOT_MATCH_ATTGROUP(500, "未查询到用户考勤组,请先配置!"),
    USER_NOT_FOUND(500, "没有查询到用户!"),

    COMPANY_ADMIN_NOT_EXISTS(500, "没有找到对应企业管理员!"),
    COMPANY_NOT_FOUND(500, "企业不存在!"),
    WROK_NUM_EXISTS(500, "当前工号已经存在!"),

    COMPANY_USER_NOT_FOUND(404, "企业员工不存在!"),
    SMS_CODE_TIMEOUT(404, "验证码超时,请重新发送!"),

    PUNCH_INVALID_AREA(500, "打卡地点不在考勤点范围内!"),
    PUNCH_INVALID_DAY(500, "非工作日无需打卡!"),
    PUNCH_ALREADY(500, "已经打卡,无需重复打卡!"),
    SIGN_DATA_NULL(404, "未检索到签到数据!"),

    MESSAGE_PARAMS_LOST(500, "查询参数缺失!"),

    UNAUTHORIZED(401, "登录失效或未登录!"),
    FOBIDDEN(403, "禁止访问!"),
    SIGNDATA_NOT_FOUND(500, "当前检索条件没有签到数据!"),

    ROLE_NOT_FOUND(403, "角色列表不存在!"),
    ROLE_SYS_NOT_FOUND(403, "系统管理员角色不存在!"),
    SYS_PERMISSSION_NOT_FOUND(403, "当前企业无权限"),


    DOC_NOT_FOUND(403, "文档不存在或者已经删除!"),
    DOC_NOT_ALLOWED(403, "只有作者才能设置协作者!"),
    FILE_NOT_ALLOWED_MODIFY(403, "您没有权限修改文档!"),
    FILE_NOT_BELONG_YOU(403, "您不是该文档拥有者,无法设置权限!"),
    ;


    //提供属性
    private Integer status;//状态码
    private String message; //提示消息


    //提供私有构造
    ResponseEnum(Integer status, String message) {
        this.status = status;
        this.message = message;
    }

    //提供get方法获取属性值

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

mybatisPlus环境

1.引入mybatisPlus依赖,mysql依赖

<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- mybatisplus与springboot整合 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

2.开启MybatisPlus包扫描-扫描持久层接口

package com.itheima;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.itheima.sys.mapper")
public class NcSysApplication {

    public static void main(String[] args) {
        SpringApplication.run(NcSysApplication.class, args);
    }

}

3.yml文件中配置数据源,mybatisPlus配置

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/nc_sys?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root

正常编写代码操作:提供实体类(PO,DTO) ,提供三层(Mapper,service,controller)完成注入,提供单表CURD代码

package com.itheima.sys.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@TableName
@Data
public class Company {

    @TableId
    private Long id;
    private String name;
    private String nameAbbr;

}

service业务层接口采用继承MybatisPlus提供统一IService,service实现了类继承ServiceImpl简化业务层代码。

package com.itheima.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.sys.entity.Company;

public interface ICompanyService extends IService<Company> {

    //提供单表CURD 分页方法


}

package com.itheima.sys.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.sys.entity.Company;
import com.itheima.sys.mapper.CompanyMapper;
import com.itheima.sys.service.ICompanyService;
import org.springframework.stereotype.Service;

@Service
public class CompanyServiceImpl extends ServiceImpl<CompanyMapper, Company> implements ICompanyService {

}

测试类如下:

package com.itheima.sys.service.impl;

import com.itheima.NcSysApplication;
import com.itheima.sys.entity.Company;
import com.itheima.sys.service.ICompanyService;
import jdk.nashorn.api.scripting.ScriptUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = NcSysApplication.class)
public class CompanyServiceImplTest {

    @Autowired
    private ICompanyService companyService;

    @Test
    public void findAll(){
        List<Company> list = companyService.list();
        System.out.println(list);
    }

}

空文件

简介

九点钟移动办公云平台:全新的移动办公云平台,主要帮助企业轻松解决:考勤统计难,排班混乱、特殊工时难以有效管理等。 移动端轻松地实现考勤统计、轻简办公、沟通协作、企业管理,能够极大地节省企业日常考勤、排班、办公、沟通、管理的成本。集知识分享、审批流程、数据协作等应用于一体的移动办公云平台 。每天要做什么、做了什么、有什么收获;实时推进目标成果、构建高效率协作团队。 展开 收起
取消

发行版

暂无发行版

贡献者 (1)

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化