• 【微服务架构】基础的微服务架构模板、fianceCampus项目


    OfferCampus前期构建简单介绍: 搭建完整的模板Spring Cloud项目


    基于SpringCloud搭建模板项目 ----- financeCampus实例


    Cfeng在构建OfferCampus项目时,最开始选用Dubbo搭建该微服务项目,最终落入单体陷阱: 微服务之间的依赖过多,形成了一个分崩离析的单体项目,效果甚至不如单体项目; 不符合微服务项目的设计理念,因此选用Spring Cloud完整的生态进行微服务项目的构建

    这里只是会简单的提及各个组件的功能和相关的使用,搭建一个微服务项目的模板供以后的服务搭建使用【 当然每个微服务都会采用Middleware进行性能提升】

    本篇文章只是简单介绍几个cfeng认为重要的点,大部分简略带过 – 该项目Cfeng也没有完全深入做,只是以此来分析创建一个微服务项目所需要做的工作,像核心的业务逻辑则是每个项目具体分析

    该项目如果有机会会深入,但是目前来看技术含量不大,但是代码的逻辑还是庞大,因此这里只是简单引入一下项目,了解微服务项目架构的基本的技术

    而offerCampus项目将会深入,同时将会增加一些个性化功能方便Cfeng自身的使用

    SpringCloud 项目

    这里的项目拆分方法采用的垂直拆分,没有按照业务拆分, 这里拆分为common、service-base、service-core; core —》 base —》 common

    但是像sms和oss等还是还是按照的业务方式,整个脚手架项目的微服务移动就只有一个核心的服务和对象存储、sms服务, 而Cfeng经手的OfferCapmus项目是按照业务划分多个微服务

    这里以Cloud搭建一个通用的后端项目,当然具体的业务逻辑需要具体分析,但是架构的代码其实是类似的

    在这里插入图片描述

    选择Alibaba的Sentinel做服务降级和监控、nacos做注册和配置中心,使用redis、rabbitMQ作为分布式缓存和消息件,使用mysql、mongoDB数据库,使用minIO做服务文件对象存储、同时使用短信服务 【部署运维采用docker】

    SpringBoot的版本选择:SpringBoot 2.4 + 与之下的版本变化比较大【比如security配置完全变化,但是可以理解】

    目前,单机项目Cfeng 采用的是2.7.2的SpringBoot版本【本地仓库Cfeng有N个版本】

    而微服务Cfeng预备采用2.6.3版本,对应的SpringCloud会采用2021.0.1,对应的Alibaba的版本也就是2021.0.1.x

    而2021.0.1.0版本的Alibaba对应的组件: Sentinel — 1.8.3, Nacos— 1.4.2, rocketMQ — 4.9.2, Seata ---- 1.4.2

    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-alibaba-dependenciesartifactId>
        <version>2021.0.1.0version>
        <type>pomtype>
        <scope>importscope>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-dependenciesartifactId>
        <version>2021.0.0version>
        <type>pomtype>
        <scope>importscope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    脚手架项目 ---- financeCapmus

    金融借贷中介平台构建 — 服务人群分为: 投资者、第三方托管平台、借款人(托管的资金池 ---- 银行就是一个资金池)

    资金有风险,如果资金池长期入量大,出量少,资金池有成本(付利息给投资者),形成庞氏骗局,中介可能会倒闭; 资金出量主要是找项目和风控,(eg:基金、股票,可能一下子就赔了),可能导致资金不能流回; 提款出水量变大 — 一个黑天鹅事件挤兑,可能导致投资者疯狂回收,导致平台垮掉; 还有中介平台把资金池钱取走、跑路…

    所以资金第三方存管需要认清资质,第三方托管平台一般都是银行(风控体系完善、国家背书,不跑路)

    financeCpmpus主要就是服务投资者和借款人的一个金融中介平台,涉及资金的流动,所以项目要求高安全性

    项目技术选型: 主要就是上面那套模板,SpringBoot,数据库ORM采用mybatis-plus(JPA也好用,但是可控性没有plus好,实际项目中使用少,但是Cfeng个人项目经常使用)

    项目结构

    该项目主要是3个微服务

    • sms: 短信微服务
    • oss: 云存储微服务
    • service: service业务又垂直拆分为多个模块,基本的就包括service-base和service-core

    创建父项目(just 管理)

    微服务一般情况下还是建立一个父级项目进行统一的管理,这里就直接新建一个maven项目,删除src和其他无关的文件夹(just for 整洁)

    然后就是统一配置管理项目的公共依赖

    
    <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.0modelVersion>
    
        
        <groupId>indv.cfenggroupId>
        <artifactId>fianceCampusartifactId>
        <version>1.0.0version>
    
        <properties>
            <spring-boot.version>2.6.3spring-boot.version>
            <spring-cloud.version>2021.0.0spring-cloud.version>
            <spring-cloud-alibaba.version>2021.0.1.0spring-cloud-alibaba.version>
            <java.version>17java.version>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
            <maven.compiler.source>${java.version}maven.compiler.source>
            <maven.conpiler.target>${java.version}maven.conpiler.target>
    
            
            <mybatis-plus.version>3.4.1mybatis-plus.version>
            <velocity.version>2.3velocity.version>
            <swagger.version>2.9.2swagger.version>
            <swagger-bootstrap-ui.version>1.9.2swagger-bootstrap-ui.version>
            <commons-lang3.version>3.9commons-lang3.version>
            <commons-fileupload.version>1.3.1commons-fileupload.version>
            <commons-io.version>2.6commons-io.version>
            <alibaba.easyexcel.version>3.0.5alibaba.easyexcel.version>
            <apache.xmlbeans.version>3.1.0apache.xmlbeans.version>
            <fastjson.version>1.2.28fastjson.version>
            <gson.version>2.8.2gson.version>
            <json.version>20170516json.version>
            <aliyun-java-sdk-core.version>4.3.3aliyun-java-sdk-core.version>
            <aliyun-sdk-oss.version>3.10.2aliyun-sdk-oss.version>
            <minIO.version>1.0.0minIO.version>
            <jodatime.version>2.10.1jodatime.version>
            <jwt.version>0.7.0jwt.version>
            <httpclient.version>4.5.1httpclient.version>
            <freemaker.version>2.3.28freemaker.version>
            <guava.version>31.1-jreguava.version>
        properties>
    
        
        <dependencyManagement>
            <dependencies>
                
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-dependenciesartifactId>
                    <version>${spring-boot.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
    
                
                <dependency>
                    <groupId>org.springframework.cloudgroupId>
                    <artifactId>spring-cloud-dependenciesartifactId>
                    <version>${spring-cloud.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
    
                
                <dependency>
                    <groupId>com.alibaba.cloudgroupId>
                    <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                    <version>${spring-cloud-alibaba.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
    
                
                <dependency>
                    <groupId>com.baomidougroupId>
                    <artifactId>mybatis-plus-boot-starterartifactId>
                    <version>${mybatis-plus.version}version>
                dependency>
                
                <dependency>
                    <groupId>com.baomidougroupId>
                    <artifactId>mybatis-plus-generatorartifactId>
                    <version>${mybatis-plus.version}version>
                dependency>
                
                <dependency>
                    <groupId>org.apache.velocitygroupId>
                    <artifactId>velocity-engine-coreartifactId>
                    <version>${velocity.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>io.springfoxgroupId>
                    <artifactId>springfox-swagger2artifactId>
                    <version>${swagger.version}version>
                dependency>
                
                <dependency>
                    <groupId>io.springfoxgroupId>
                    <artifactId>springfox-swagger-uiartifactId>
                    <version>${swagger.version}version>
                dependency>
                
                <dependency>
                    <groupId>com.github.xiaoymingroupId>
                    <artifactId>swagger-bootstrap-uiartifactId>
                    <version>${swagger-bootstrap-ui.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>org.apache.commonsgroupId>
                    <artifactId>commons-lang3artifactId>
                    <version>${commons-lang3.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>commons-fileuploadgroupId>
                    <artifactId>commons-fileuploadartifactId>
                    <version>${commons-fileupload.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>commons-iogroupId>
                    <artifactId>commons-ioartifactId>
                    <version>${commons-io.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>com.alibabagroupId>
                    <artifactId>easyexcelartifactId>
                    <version>${alibaba.easyexcel.version}version>
                dependency>
                
                <dependency>
                    <groupId>org.apache.xmlbeansgroupId>
                    <artifactId>xmlbeansartifactId>
                    <version>${apache.xmlbeans.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>com.alibabagroupId>
                    <artifactId>fastjsonartifactId>
                    <version>${fastjson.version}version>
                dependency>
                <dependency>
                    <groupId>org.jsongroupId>
                    <artifactId>jsonartifactId>
                    <version>${json.version}version>
                dependency>
                <dependency>
                    <groupId>com.google.code.gsongroupId>
                    <artifactId>gsonartifactId>
                    <version>${gson.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>com.aliyungroupId>
                    <artifactId>aliyun-java-sdk-coreartifactId>
                    <version>${aliyun-java-sdk-core.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>com.aliyun.ossgroupId>
                    <artifactId>aliyun-sdk-ossartifactId>
                    <version>${aliyun-sdk-oss.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>com.indCfenggroupId>
                    <artifactId>minio-spring-boot-starterartifactId>
                    <version>${minIO.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>joda-timegroupId>
                    <artifactId>joda-timeartifactId>
                    <version>${jodatime.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>io.jsonwebtokengroupId>
                    <artifactId>jjwtartifactId>
                    <version>${jwt.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>org.apache.httpcomponentsgroupId>
                    <artifactId>httpclientartifactId>
                    <version>${httpclient.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>org.freemarkergroupId>
                    <artifactId>freemarkerartifactId>
                    <version>${freemaker.version}version>
                dependency>
    
                
                <dependency>
                    <groupId>com.google.guavagroupId>
                    <artifactId>guavaartifactId>
                    <version>${guava.version}version>
                dependency>
    
            dependencies>
        dependencyManagement>
    
        
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
    
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237

    这里只说明一点,那就是DenpendencyManagement中的依赖主要是供版本号管理,子项目按需导入时,不需要再写版本号,和denpendencies不同,其中的依赖不会自动加入到子项目中

    父项目的主要作用就是组织依赖version管理,dependencies中的依赖会自动加入每一个子项目,而management中的只是为声明version, 子项目导入依赖都不需要写version

    创建公共模块finance-common

    注意微服务项目要尽可能减少耦合,所以这里的finance中不是为了像dubbo一样接口工程存放实体类和… , 微服务项目中确实有需要共享的类型 ------ 项目通用version【为了防止各自定义不同,比如Resp】

    但是一般的实体类和其余的java类都不要耦合,避免出现一致和不一致的问题

    定义统一响应类型Resp,统一的异常处理【定义在common模块中的注解要想被扫描,必须使用@ComponentScan进行指定位置】 异常分为到达处理器前的异常,和之后的异常

    创建base模块 finance-service-base

    该模块是垂直拆分的,下属模块为公共模块,又支撑service-core模块, 该模块定义swagger的配置

    创建core模块finance-service-core

    core模块为项目的核心业务逻辑,和service-base一起组成最核心的业务微服务,调用其他的微服务

    EsayExcel

    java领域中解析、生成Excel的一个开源的框架,相比其余的产品,主要的特点为使用简单、节省内存,采用按行解析模式,将一行的结果解析通过观察者模式通知处理AnalysisEventListener

     <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>easyexcelartifactId>
            <version>2.1.7version>
        dependency>
    
    <dependency>
            <groupId>org.apache.xmlbeansgroupId>
            <artifactId>xmlbeansartifactId>
            <version>3.1.0version>
        dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建对应的写入excel表的实体类,直接@Data, 在需要作为字段名的属性使用@ExcelProperty指定即可,比如@ExcelProperty(“姓名”)

    而写入数据进入excel很简单,直接先新建.xlsx文件,之后使用EasyExcel.write方法指定fileName和该excel对应的实体类类型

    EasyExcel.write(fileName, ExcelStudentDTO.class).excelType(ExcelTypeEnum.XLS).sheet("模板").doWrite(data());
    
    EasyExcel.write(fileName, ExcelStudentDTO.class).sheet("模板").doWrite(data());
        }
        
        //辅助方法
        private List<ExcelStudentDTO> data(){
            List<ExcelStudentDTO> list = new ArrayList<>();
    
            //算上标题,做多可写65536行
            //超出:java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
            for (int i = 0; i < 65535; i++) {
                ExcelStudentDTO data = new ExcelStudentDTO();
                data.setName("Helen" + i);
                data.setBirthday(new Date());
                data.setSalary(123456.1234);
                list.add(data);
            }
    
            return list;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    xls版本的Excel一次性最多写0…65535行, xlsx版本最多1048575

    读取数据直接创建监听器,继承AnalysisEventListener,监听的类型为对用的实体类类型,invoke中就会监听到数据,doAfterAllAnalysed就是所有的数据完成后执行

    public class ExcelDictDtoListener extends AnalysisEventListener<ExcelDictDto> {
    
        /**
         * 每间隔5条存储数据库,可以加大为3000条,之后清理list,进行内存回收
         */
        private static final int BATCH_COUNT = 5;
    
        List<ExcelDictDto> list = new ArrayList<>();
    
        private DictMapper dictMapper;
    
        //构造器注入dictMapper
        public ExcelDictDtoListener(DictMapper dictMapper) {
            this.dictMapper = dictMapper;
        }
    
        /**
         * 按行读取记录
         */
        @Override
        public void invoke(ExcelDictDto excelDictDto, AnalysisContext analysisContext) {
            log.info("解析到一条记录:{}",excelDictDto);
            list.add(excelDictDto);
            //每次解析一条数据之后,就检查是否达到边界Batch,如果超过就要清理内存
            if(list.size() >= BATCH_COUNT) {
                //存数据
                this.saveData();
                //清理空间
                list.clear();
            }
        }
    
        /**
         * 所有数据读取完成
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            //需要持久化到数据库,确保内存中的数据都是持久化成功
            this.saveData();
            log.info("所有数据解析完成");
        }
    
        /**
         * 持久化
         */
        private void saveData() {
            log.info("本次持久化{}条数据",list.size());
            dictMapper.insertBatch(list);
            log.info("批量持久化成功");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    之后调用EasyExcel.read方法进行读取

    EasyExcel.read(fileName, ExcelStudentDTO.class, new ExcelStudentDTOListener()).sheet().doRead();
    
    • 1
    数据字典

    数据字典负责管理系统常用的分类数据或者固定的数据【省市区三级级联查询、民族、学历】, 主要是为了管理相关的通用的数据

    CREATE TABLE `dict`  (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '上级id',
      `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '名称',
      `value` int(11) NULL DEFAULT NULL COMMENT '值',
      `dict_code` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编码',
      `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
      `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
      `is_deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标记(0:不可用 1:可用)',
      PRIMARY KEY (`id`) USING BTREE,
      UNIQUE INDEX `uk_parent_id_value`(`parent_id`, `value`) USING BTREE,
      INDEX `idx_parent_id`(`parent_id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 82008 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '数据字典' ROW_FORMAT = DYNAMIC;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通过id和parent_id构建上下级关系, name就是名称【填写用户信息,select选择民族,汉族就是名称】,value【比如用1代表汉族】,dict_code就是编码,全局唯一,比如学历数据education、收入来源数据returnSource, 通过数据字典配合EasyExcel就可以实现数据的导入和显示

    访问令牌JWT

    • 单一服务器模式: 在单体应用中,访问令牌的过程: 用户向服务器发送用户名和密码 —> 验证服务器后,相关的数据保存在当前session中 —> 服务器向用户返回session_id, session信息写入cookie —> 用户的后续请求都会在cookie中取出session_id传给服务器供验证身份

    但是在微服务项目中,要想正确完成令牌验证,只能使用Session共享 ----- 搭建redis集群作为session集群

    • SSON(single Sign On)模式: OAuth2、CAS单点登录, 也就是统一的一个认证中心进行身份的验证 【在多个应用系统中,只需要登录一次,就可以访问其他互相信赖的应用系统】 当业务A、B需要登录时,跳转到SSO系统完成登录 —> 用户信息写入缓存 —> 访问业务,跳转SSO验证,判断缓存中是否存在

    但是这里的缺点就是因为进行统一的认证,所以认证服务器的压力较大

    • Token模式: Client请求授权服务器返回token,利用token进行登录

    JWT令牌 — JSON WEB Token 自包含令牌, 使用在webapi、web服务器无状态分布式身份验证

    JWT最重要的功能就是token防伪, 一个JWT由Headr、payload、签名哈希 组成,之后base64编码得到jwt【通过.分隔为3个字串】

    JET头时是描述元数据的JSON对象,比如alg 代表签名算法,默认HS256,typ表示令牌类型,比如JWT

    payload就是内容部分,也是json对象,包含传递的对象,指定七个默认字段: sub主题、iss签发者、aud接收方、iat签发时间、exp过期时间、nbf什么时间之前jwt不可用、jti唯一的身份表示,一次性token,避免重放

    还可以自定义数据,比如name、password…

    JWT默认不加密,任何人可以解读,所以不要构建私密信息的字段,防止信息泄露

    签名HASH就是利用alg生成hash进行签名,数据不会被篡改,相当于类似之前的

    查看源图像

    JWT - test使用

    	<dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.7.0version>
        dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用JWT很简单,通过JWTs的builder创建一个JWT的token,set指定header和相关的参数

    public class JwtTests {
    
        //设置token的过期时间
        private static long tokenExpiration = 24 * 60 * 60 * 1000;
    
        //密钥
        private static String tokenSignKey = "wZ0jH0";
    
        @Test
        public void testCreateJwtToken() {
            String token = Jwts.builder()
                    .setHeaderParam("typ","JWT") //令牌类型
                    .setHeaderParam("alg", "HS256") //签名算法
    
                    .setSubject("sys-user")  //令牌主题
                    .setIssuer("cfeng")    //签发者
                    .setAudience("cfeng")   //接收者
                    .setIssuedAt(new Date())
                    .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) //过期时间
                    .setNotBefore(new Date(System.currentTimeMillis() + 20 * 1000))  //20s后才可用
                    .setId(UUID.randomUUID().toString()) //令牌的唯一标识
                    //自定义负载
                    .claim("nickname", "java") //昵称
                    .claim("avatar", "1.jpg") //头像
    
                    .signWith(SignatureAlgorithm.HS256,tokenSignKey) //使用密钥按照HS256加密
                    .compact();   //转为字符串,生成了token
    
            System.out.println(token);
        }
    
        /**
         * 解析token
         */
        @Test
        public void testGetTokenInfo() {
            String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzeXMtdXNlciIsImlzcyI6ImNmZW5nIiwiYXVkIjoiY2ZlbmciLCJpYXQiOjE2NjcyNjkxMzYsImV4cCI6MTY2NzM1NTUzNiwibmJmIjoxNjY3MjY5MTU2LCJqdGkiOiJkZGNlZGFkZS02MWE2LTQ2ZTEtYjBiMC0yNTM0OTZkNGI5MWIiLCJuaWNrbmFtZSI6ImphdmEiLCJhdmF0YXIiOiIxLmpwZyJ9.ycXa-6z4YsW3Jwy45cj6EfyQmyfo7fX6_8y-WM1LVgQ";
            //解析JWT
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
    
            //获取负载payload
            Claims claims = claimsJws.getBody();
    
            //该claims就是一个集合,从中获取即可
            String subject = claims.getSubject();
            String issuer = claims.getIssuer();
            String audience = claims.getAudience();
            Date issueAt = claims.getIssuedAt();
            Date expiration = claims.getExpiration();
            Date notBefore = claims.getNotBefore();
            String id = claims.getId();
            String nickname = (String) claims.get("nickname");
    
            System.out.println(subject + "," + issuer + "," + audience + "," +  issueAt + "," + expiration + "," + notBefore + "," + id + "," + nickname);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    生成Token的时候就是利用Jwts.builder之后set注入相关的header、payload,同时指定加密的alg和signKey

    而解析jwt令牌则是使用Jwts.parses指定signKey之后转化为jwtClaims即可,该claims的body就是payload,解析出相关的参数即可

    使用JWT格式的token可以让token更加规范化,使用简单的时间戳可能存在安全问题,JWT加密之后可以防止篡改

    邮箱登录

    这里以QQ邮箱为例,要开通SMTP服务,生成一个对应的权限码

    引入mail的依赖

    	<dependency>
               <groupId>org.springframework.bootgroupId>
               <artifactId>spring-boot-starter-mailartifactId>
           dependency>
    
    • 1
    • 2
    • 3
    • 4

    配置mail的yml配置项

    spring:
      mail:
        host: smtp.qq.com #平台地址,这里使用的qq邮箱
        username: 158xxxxxxxx9@qq.com  #发送邮件的邮箱
        password: dldgyXXXXXXch   #发送的校验码,邮箱密码
        default-encoding: UTF-8
        properties:
          mail:
            smtp:
              ssl:
                enabled: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    之后就可以利用自动配置的mailSender对象send邮件,普通邮件SimpleMessage,带附件邮件MimeMessage, 普通邮件直接new之后set注入,而附件邮件则是通过sender构建,并且使用MimeHelper进行set注入

    @SpringBootTest
    public class MailTest {
    
        //邮件服务发送
        @Resource
        private JavaMailSender mailSender;
    
        @Test
        public void sendMail() {
            try {
                SimpleMailMessage mailMessage = new SimpleMailMessage();
                mailMessage.setSubject("验证码邮件"); //主题
                String code = "545345";
                mailMessage.setText("你的验证码是: " + code);
                mailMessage.setFrom("1XXXX8XXX79@qq.com"); //发送方
                mailMessage.setTo("2XXXXX89@qq.com"); //发给谁
    
                System.out.println("邮件发送成功");
                mailSender.send(mailMessage);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Test
        public void sendAttachmentMail() {
            File file = new File("D:\\TransferStation\\zjn.pdf");
            //附件文件对象FileSystemResource读取成文件资源
            FileSystemResource resource = new FileSystemResource(file);
    
            //通过sender自动对象创建MimeMessage,和SimpMessage不同
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            //使用mimeMessageHelper封装附件形式的邮件
            try {
                //multipart多文件
                MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
                helper.setSubject("请查收zjn的简历");
                helper.setText("你的验证码是: 8987863" + " 查看附件的简历");
                helper.setFrom("15XXXX2749@qq.com");
                helper.setTo("2326XXX289@qq.com");
                helper.addAttachment("zjn简历",resource);
            } catch (MessagingException e) {
                e.printStackTrace();
            }
            mailSender.send(mimeMessage);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
  • 相关阅读:
    Springboot 整合 Elasticsearch(五):使用RestHighLevelClient操作ES ②
    ECharts数据可视化项目【6】
    si446使用记录(二):使用WDS3生成头文件
    【目标检测】Faster R-CNN 论文复现代码(含源代码)
    【大咖说Ⅱ】中科院信工所研究员林政:大规模预训练语言模型压缩技术
    JTS:04 读取数据库数据
    下载文本标注工具doccano遇到的报错以及解决方案
    Android EventBus 事件订阅/发布框架
    11.20 至 11.27 五道典型题记录: 贪心 | 应用题 | 脑筋急转弯 | 区间问题 | 双指针
    干货!自动驾驶场景下的多目标追踪与实例分割
  • 原文地址:https://blog.csdn.net/a23452/article/details/127629432