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.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2021.0.1.0</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>2021.0.0</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

脚手架项目 ---- financeCapmus

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

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

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

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

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

项目结构

该项目主要是3个微服务

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

创建父项目(just 管理)

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

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

<?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>

    <!-- fianceCampus 管理模块父模块 公共依赖和依赖管理 -->
    <groupId>indv.cfeng</groupId>
    <artifactId>fianceCampus</artifactId>
    <version>1.0.0</version>

    <properties>
        <spring-boot.version>2.6.3</spring-boot.version>
        <spring-cloud.version>2021.0.0</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.conpiler.target>${java.version}</maven.conpiler.target>

        <!-- management 中依赖的版本号  3.5.0之后的mybatis-plus有点不一样-->
        <mybatis-plus.version>3.4.1</mybatis-plus.version>
        <velocity.version>2.3</velocity.version>
        <swagger.version>2.9.2</swagger.version>
        <swagger-bootstrap-ui.version>1.9.2</swagger-bootstrap-ui.version>
        <commons-lang3.version>3.9</commons-lang3.version>
        <commons-fileupload.version>1.3.1</commons-fileupload.version>
        <commons-io.version>2.6</commons-io.version>
        <alibaba.easyexcel.version>3.0.5</alibaba.easyexcel.version>
        <apache.xmlbeans.version>3.1.0</apache.xmlbeans.version>
        <fastjson.version>1.2.28</fastjson.version>
        <gson.version>2.8.2</gson.version>
        <json.version>20170516</json.version>
        <aliyun-java-sdk-core.version>4.3.3</aliyun-java-sdk-core.version>
        <aliyun-sdk-oss.version>3.10.2</aliyun-sdk-oss.version>
        <minIO.version>1.0.0</minIO.version>
        <jodatime.version>2.10.1</jodatime.version>
        <jwt.version>0.7.0</jwt.version>
        <httpclient.version>4.5.1</httpclient.version>
        <freemaker.version>2.3.28</freemaker.version>
        <guava.version>31.1-jre</guava.version>
    </properties>

    <!-- 依赖管理,子项目使用时不需要写版本号version -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--Spring Cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--Spring Cloud Alibaba-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--mybatis-plus-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!--mybatis-plus 代码生成器-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!-- Mybatis Plus 代码生成器模板引擎,  -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>${velocity.version}</version>
            </dependency>

            <!--swagger-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--swagger ui-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--swagger-bootstrap-ui-->
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>swagger-bootstrap-ui</artifactId>
                <version>${swagger-bootstrap-ui.version}</version>
            </dependency>

            <!--commons-lang3-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons-lang3.version}</version>
            </dependency>

            <!--文件上传-->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>${commons-fileupload.version}</version>
            </dependency>

            <!--commons-io-->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons-io.version}</version>
            </dependency>

            <!--excel解析-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>${alibaba.easyexcel.version}</version>
            </dependency>
            <!--excel解析依赖-->
            <dependency>
                <groupId>org.apache.xmlbeans</groupId>
                <artifactId>xmlbeans</artifactId>
                <version>${apache.xmlbeans.version}</version>
            </dependency>

            <!--json-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
            <dependency>
                <groupId>org.json</groupId>
                <artifactId>json</artifactId>
                <version>${json.version}</version>
            </dependency>
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson.version}</version>
            </dependency>

            <!--阿里云SDK远程调用-->
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>${aliyun-java-sdk-core.version}</version>
            </dependency>

            <!--阿里云文件管理-->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun-sdk-oss.version}</version>
            </dependency>

            <!-- 自定义starter minIO -->
            <dependency>
                <groupId>com.indCfeng</groupId>
                <artifactId>minio-spring-boot-starter</artifactId>
                <version>${minIO.version}</version>
            </dependency>

            <!--日期时间工具-->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>${jodatime.version}</version>
            </dependency>

            <!--jwt工具-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>

            <!--httpclient-->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>${httpclient.version}</version>
            </dependency>

            <!-- freemaker依赖-->
            <dependency>
                <groupId>org.freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>${freemaker.version}</version>
            </dependency>

            <!-- guava工具类,提供很多使用工具google,包括限流工具-->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>${guava.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <!-- 公用的依赖就是test-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

这里只说明一点,那就是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.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.1.7</version>
    </dependency>

<dependency>
        <groupId>org.apache.xmlbeans</groupId>
        <artifactId>xmlbeans</artifactId>
        <version>3.1.0</version>
    </dependency>

创建对应的写入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;
    }

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("批量持久化成功");
    }
}

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

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

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

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;

通过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.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.7.0</version>
    </dependency>

使用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);
    }
}

生成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.boot</groupId>
           <artifactId>spring-boot-starter-mail</artifactId>
       </dependency>

配置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

之后就可以利用自动配置的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);
    }

}

更多推荐