智牛股_第8章_Sentinel

news2025/1/16 2:00:27

智牛股_第8章_Sentinel

文章目录

  • 智牛股_第8章_Sentinel
    • 学习目标
      • 第1章 Sentinel集成使用
      • 1. 目标
      • 2. 步骤
      • 3. 实现
        • 3.1 生产环境最优配置方案
        • 3.2 用户服务集成
        • 3.3 熔断规则配置
        • 3.4 启动Sentinel监控台
        • 3.5 功能使用验证
      • 4. 总结
      • 第2章 用户注册功能
      • 1. 目标
      • 2. 步骤
      • 3. 实现
        • 3.1 用户注册流程
        • 3.2 数据库结构
        • 3.3 创建实体
        • 3.4 创建公用系统层组件
        • 3.5 数据层
        • 3.6 全局序列号生成方案
        • 3.7 服务层
        • 3.8 接入层实现
        • 3.9 添加自动化校验
        • 3.10 启动服务
        • 3.11 功能验证
      • 4. 总结

学习目标

目标1:Sentinel最优生产实践方案
目标2:用户服务集成Sentinel使用
目标3:用户注册业务功能设计与代码实现
目标4: 全局序列号生成使用
目标5: 自动化校验使用

第1章 Sentinel集成使用

1. 目标

  • 掌握Sentinel生产环境最佳配置实现方案

  • Sentinel与用户服务集成配置, 完成整体功能验证

2. 步骤

  • 生产环境最优配置方案
  • 用户服务集成
  • 熔断规则配置
  • 启动Sentinel监控台
  • 功能使用验证

3. 实现

第二天的课程已经讲过Sentinel的使用与原理, 接下来我们在Spring Cloud 微服务的生产环境中配置和运用Sentinel。

3.1 生产环境最优配置方案

配置模式选择:

推送模式说明优点缺点
原始模式API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource简单,无任何依赖不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
Pull 模式扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等简单,无任何依赖;规则持久化不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。
Push 模式扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。规则持久化;一致性;快速引入第三方依赖
  1. 原始模式

在这里插入图片描述

  1. Pull拉取模式

在这里插入图片描述

  1. Push推送模式

在这里插入图片描述

优点: 基于较强的实时性,支持动态修改发布,重启服务不受影响, 具有较高的可靠性与一致性。

官方建议采用Push模式。

3.2 用户服务集成

  1. 功能设计
    在这里插入图片描述

    我们把上一章完成的用户接口进行改造, 加入降级与限流处理。

    整个部署结构采用PUSH模式, 配置规则放置Nacos配置中心,统一发布与更新。

    在生产环境中, 如果在峰值时间段, 出现大量用户请求或者内部系统出现问题, 这个时候就有必要进行限流和降级处理, 防止穿透、雪崩导致整个服务瘫痪。

  2. 修改MAVEN配置

    pom.xml增加以下依赖:

        <!-- Sentinel 限流组件 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
    

    由于采用Nacos作为Sentinel的数据源, sentinel-datasource-nacos该依赖须加上。

  3. 修改用户登陆接口

    修改StockUserServiceImpl类:

    
        /**
         * 用户登陆限流处理逻辑
         * @param userNo
         * @param userPwd
         * @param ex
         * @return
         * @throws BlockException
         */
        public TradeUser userLoginHanlder(String userNo, String userPwd, BlockException ex ) throws ComponentException {
            log.error("userLogin flow limit, call userLoginHandler: " + ex.getMessage());
            // 1. 获取熔断限流规则
            AbstractRule abstractRule = ex.getRule();
            // 2. 根据不同规则, 进行不同逻辑处理
            if(abstractRule instanceof DegradeRule ) {
                throw new ComponentException(ApplicationErrorCodeEnum.SYS_BUSY);
            }else if(abstractRule instanceof FlowRule) {
                throw new ComponentException(ApplicationErrorCodeEnum.SYS_FLOW);
            }
            // 3. 默认, 未捕获, 不符合配置的规则, 进入系统异常
            throw new ComponentException(ApplicationErrorCodeEnum.FAILURE);
        }
    
    
       /**
         * 用户登陆
         * @param userNo
         * @param userPwd
         * @return
         */
        @SentinelResource(value ="userLogin", blockHandler = "userLoginHanlder")
        public TradeUser userLogin(String userNo, String userPwd) throws Exception {
    
            // 模拟降级异常
            if("error".equals(userNo)) {
                throw new ComponentException(ApplicationErrorCodeEnum.FAILURE);
            }
    
            // 获取用户对象
            TradeUser tradeUser= stockUserDao.getByUserNo(userNo);
            if(null == tradeUser) {
                throw new ComponentException(ApplicationErrorCodeEnum.USER_NOT_FOUND);
            }
    
            // 用户密码加密判断
            String encryptPassword = EncryptUtil.encryptSigned(userPwd);
            boolean pwdMatch= tradeUser.getUserPwd().equals(encryptPassword);
            if(!pwdMatch) {
                log.error(ApplicationErrorCodeEnum.USER_PWD_ERROR);
                throw new ComponentException(ApplicationErrorCodeEnum.USER_PWD_ERROR);
            }
    
            return tradeUser;
        }
    

    采用SentinelResource注解, 定义资源名称与降级方法。注意1.6.0以下版, 降级只支持DegradeException异常, 在使用上有些差别, 但功能是一致。

    用户登陆方法如果符合降级规则, 会进入userLoginHanlder方法, 里面返回一个null对象。为了方便验证, 在代码里面我们模拟了一个异常, 抛出ApplicationErrorCodeEnum.FAILURE错误码。

    使用ComponentException, 不要抛出DegradeException, 否则只要出现该异常就会进入降级, 配置的降级规则条件(比如根据异常比例触发)就不会生效。

    注意: 如果在同一个方法上面, 同时要配置降级和限流的处理规则, 它优先会进入blockHandler定义的方法,如果没有定义blockHandler或者fallback 等任何熔断限流处理属性, 那么它会抛出BlockException异常。

    有关注解的详细配置, 可参考: 官方SentinelResource注解说明

    修改StockUserController类:

     /**
         * 用户登陆接口
      * @param userNo
         * @param userPwd
         * @return
         */
        @RequestMapping("/userLogin")
        public ApiRespResult userLogin(@RequestParam("userNo")String userNo, @RequestParam("userPwd") String userPwd) {
    
            ApiRespResult  result = null;
            try {
                // 用户登陆逻辑处理
                TradeUser tradeUser = stockUserService.userLogin(userNo, userPwd);          
                result = ApiRespResult.success(tradeUser);
            }catch(ComponentException e) {
                log.error(e.getMessage(), e);
                result = ApiRespResult.error(e.geterrorCodeEnum());
            }catch(Exception e) {
                log.error(e.getMessage(), e);
                result = ApiRespResult.sysError(e.getMessage());
            }
    
            return result;
    
        }
    

    外围增加熔断降级和限流的异常捕获, 1.6以下版本的限流异常为FlowException, 返回错误提示给客户端。

  4. 修改工程配置

    修改bootstrap.xml配置文件:

    spring:
      application:
        name: stock-user
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
          config:
            server-addr: 127.0.0.1:8848
        sentinel:
          transport:
            # Sentinel监控台地址
            dashboard: 127.0.0.1:8090
          datasource:
            # 用户降级规则配置
            user-degrade:
              nacos:
                server-addr: 127.0.0.1:8848
                dataId: sentinel-user-degrade
                groupId: DEFAULT_GROUP
                data-type: json
                rule-type: degrade
            # 用户限流规则配置
            user-flow:
              nacos:
                server-addr: 127.0.0.1:8848
                dataId: sentinel-user-flow
                groupId: DEFAULT_GROUP
                data-type: json
                rule-type: flow
    

    以上主要增加Sentinel监控台配置, 用户降级规则配置和用户限流规则配置。

    这是1.6以下版本的配置, 和最新版会有所不同, 官方文档一般是最新版, 参看时要注意。

3.3 熔断规则配置

启动Nacos服务, 在配置管理里面新建两项配置:

在这里插入图片描述

sentinel-user-degrade为降级配置策略, 内容:

[
  {
    "resource": "userLogin",
    "count": 0.2,
    "grade": 1,    
    "timeWindow": 4
  }
]

resource: 为资源名称。

count: 为百分比[0-1], 这里代表20%

grade: 为降级策略, 0: 代表响应时间, 1: 代表异常比例, 2: 代表异常数量, 这里采用的是异常比。

timeWindow:为时间窗, 单位为秒。

sentinel-user-flow为限流配置策略: 内容:

[
   {
    "resource": "userLogin",
    "controlBehavior": 0,
    "count": 12,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0
  }
]

resource: 为资源名称。

controlBehavior:流量整形的控制效果,目前支持快速失败和匀速排队两种模式,默认是0, 快速失败。

count: 线程数量。

grade:限流配置策略, 0:代表线程数量, 1:代表QPS并发数。

limitApp: 限流针对的来源, 填写default即可。

strategy: 基于调用关系的流量控制策略, 有三种

0-STRATEGY_DIRECT,根据调用方进行限流, 结合limitApp使用。

1-STRATEGY_RELATE, 根据关联流量限流, 当多个资源间具有资源争抢和关联关系的时候,比如同一个数据表的读与写请求, 如果写操作比较频繁, 那么读数据的请求就会被限流处理。

2- STRATEGY_CHAIN, 根据调用链的入口限流, 比如两个请求Req1和Req2, 同时再配合设置FlowRule.refResource 指定Req1为入口请求, 那么Req1就会受到限流控制, Req2则可放行。

注意配置内容的JSON格式要符合要求, 如果填写错误, 配置不会生效。

3.4 启动Sentinel监控台

执行命令:

java  -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090  -jar sentinel-dashboard-1.6.2.jar

端口设置为8090, 监控台采用最新版本1.6.2。

启动成功后, 监控台不设置任何策略, 基于push模式, 由Nacos配置中心统一维护更新。

在这里插入图片描述

3.5 功能使用验证

  1. 启动用户服务

    如果策略配置通过Nacos获取成功, 会有以下提示信息,如果没有提示(新版本会变化)也没关系, 能够在监控台显示即可。

在这里插入图片描述

我们在Nacos配置的一条降级策略与一条限流策略加载成功。

  1. 启动认证服务

    上一章我们把用户服务集成了OAUTH2, 需要把认证服务启动, 并申请TOKEN。

  2. Sentinel监控台

    由于Sentinel采用懒加载方式设计, 如果启动服务成功, 监控台不能发现,先调用一次服务接口即可。

    进入监控台查看规则, 可以看到都已经同步拉取下来:

在这里插入图片描述

与Nacos中配置的内容一致。

  1. 限流策略验证

    我们配置了限流和降级两个策略, 便于演示, 把两个策略规则修改下, 在Nacos中把限流策略的QPS并发数设为2。在重新查看Sentinel控制台, 可以看到能够动态更新。

在这里插入图片描述

构造两个请求,

一个是正常访问: 

http://127.0.0.1:10681/user/userLogin?userNo=admin&userPwd=admin&access_token=8c86d26e-1152-40d1-8b0c-bc40d2047b37

在这里插入图片描述

一个是会触发异常的访问:

http://127.0.0.1:10681/user/userLogin?userNo=error&userPwd=admin&access_token=8c86d26e-1152-40d1-8b0c-bc40d2047b37

在这里插入图片描述

限流策略与是否触发异常无关, 拿正常访问链接, 加快刷新频率, 可以看到出现了限流的错误提示:
在这里插入图片描述

  1. 降级策略验证:

    先把QPS放大一些, 重新设为12, 便于我们进行降级策略的演示。

    上面设置的降级策略条件是异常比为20%, 时间窗口为4s, 开始验证。

    请求会触发异常的访问链接, 多刷新几次, 加快请求频率, 可以看到出现降级错误提示:

在这里插入图片描述

再快速访问正常链接, 可以看到也出现降级错误提示(时间窗口4s较短, 不能间隔太长时间访问):

在这里插入图片描述

过完时间窗之后, 重新访问, 恢复正常:

在这里插入图片描述

4. 总结

  • 在生产环境中采用Push模式,优点支持规则持久化, 较强的一致性与可靠性, 能够快速处理, 实现最佳生产配置, 适用大型生产项目中使用, 掌握与理解熔断规则配置, 结合Sentinel监控台, 更好的发挥Sentinel组件作用。

第2章 用户注册功能

1. 目标

  • 了解用户注册流程与数据库结构

  • 掌握全局序列号生成方案, 自动化校验实现

  • 完成用户注册功能与验证

2. 步骤

  • 用户注册流程介绍
  • 数据库结构设计
  • 创建实体
  • 创建公用系统层组件
  • 数据层实现
  • 服务层实现
  • 全局序列号生成方案
  • 接入层实现
  • 自动化校验实现
  • 服务启动与功能验证

3. 实现

3.1 用户注册流程

在这里插入图片描述

这是一个业界常用的注册流程,一般会有代理系统,提案信息,审批功能, 这个一期我们简化,直接实现用户注册功能, 业务设计上是先注册后开户, 这里通过开关来控制, 如果注册即开户, 会生成开户账号; 本章节, 我们保留此开关, 实现注册即开户的功能。

3.2 数据库结构

在这里插入图片描述

增加用户文件表:

drop table if exists t_trade_user_file;

/*==============================================================*/
/* Table: t_trade_user_file                                     */
/*==============================================================*/
create table t_trade_user_file
(
   id                   bigint not null auto_increment  comment '主键标识',
   userId               bigint(16) not null  comment '用户ID',
   bizType              tinyint(3) not null  comment '业务类型(0:身份证, 1:银行卡, 2:信用卡)',
   fileId               varchar(32) not null  comment '文件ID',
   filename             varchar(64)  comment '文件名称',
   fileType             varchar(32)  comment '文件类型',
   filePath             varchar(255)  comment '文件路径',
   status               tinyint(3) not null  comment '状态(0:有效, 1:无效)',
   createTime           datetime  comment '创建时间',
   updateTime           datetime not null default CURRENT_TIMESTAMP  comment '更新时间',
   primary key (id)
);

alter table t_trade_user_file comment '用户文件表';

/*==============================================================*/
/* Index: idx_userId                                            */
/*==============================================================*/
create index idx_userId on t_trade_user_file
(
   userId
);

增加公司表:

drop table if exists t_company;

CREATE TABLE `t_company` (
  `id` bigint(19) NOT NULL AUTO_INCREMENT COMMENT '主键标识',
  `companyName` varchar(32) DEFAULT NULL COMMENT '公司名称\r\n             ',
  `institutionTypeId` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '机构类型',
  `contactUser` varchar(32) NOT NULL COMMENT '联系人',
  `contactPhone` varchar(32) DEFAULT NULL COMMENT '联系电话',
  `adminUser` varchar(32) DEFAULT NULL COMMENT '管理员账号',
  `status` tinyint(3) NOT NULL COMMENT '状态(0:有效, 2:禁用)',
  `createUser` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '创建人名称',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  `lastUpdateUser` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '最后更新人名称',
  `lastUpdateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_accountNo` (`contactUser`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='公司(交易商)表';



增加机构表:

drop table if exists t_institution;
CREATE TABLE `t_institution` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '机构id',
  `institutionTypeId` varchar(48) DEFAULT NULL COMMENT '机构类型id',
  `detailInstitutionId` bigint(20) DEFAULT NULL COMMENT '机构关联id',
  `detailInstitutionName` varchar(255) DEFAULT NULL COMMENT '机构关联名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='机构表';


增加系统全局配置表:

drop table if exists t_trade_global_config;

/*==============================================================*/
/* Table: t_trade_global_config                                 */
/*==============================================================*/
create table t_trade_global_config
(
   id                   bigint not null auto_increment  comment '主键标识',
   code                 varchar(32)  comment '配置项编号',
   category             varchar(32)  comment '分类编号(BASIC:基础配置, BUSINESS: 业务配置,  SYSTEM:系统项配置)',
   value                varchar(128)  comment '配置项的值',
   status               tinyint(2) not null  comment '状态(0:启用, 1:禁用)',
   primary key (id)
);

alter table t_trade_global_config comment '系统全局配置表';

/*==============================================================*/
/* Index: idx_key                                               */
/*==============================================================*/
create index idx_key on t_trade_global_config
(
   code
);

INSERT INTO `trade_stock`.`t_trade_global_config`(`id`, `code`, `category`, `value`, `status`) VALUES (1, 'REG_OPEN_ACCOUNT', 'SYSTEM', 'N', 0);

增加序列表:

drop table if exists t_seq;

/*==============================================================*/
/* Table: t_seq                                                 */
/*==============================================================*/
create table t_seq
(
   id                   bigint not null auto_increment  comment '主键标识',
   code                 varchar(32)  comment '配置项编号',
   value                bigint(21)  comment '序列值',
   primary key (id)
);

alter table t_seq comment '序列表';

/*==============================================================*/
/* Index: idx_code                                              */
/*==============================================================*/
create index idx_code on t_seq
(
   code
);

用户注册与开户是两个功能, 这里我们实现用户注册即开户的功能,会生成一个交易帐号, 通过开关控制。 因涉及到开关配置参数以及将来的系统参数配置处理, 需要用到系统全局配置表。

3.3 创建实体

  1. 修改Mybatis Generator 配置文件, 生成t_trade_account、t_trade_global_config、 t_trade_user_file、t_company、t_institution实体与数据层代码。

  2. 创建TradeAccount与TradeUserFile:
    在这里插入图片描述

3.4 创建公用系统层组件

在这里插入图片描述

这里放置系统全局配置的处理, 便于各微服务模块的复用。

3.5 数据层

创建DAO与MAPPER文件:
在这里插入图片描述

将生成的代码做相应调整与修改, 数据层提供查询与创建接口。

创建用户注册逻辑相关接口:

  • 校验用户是否已经注册接口:

    IStockUserDao:

        /**
         * 校验用户是否已经注册(包括手机号, 邮箱, 用户编号)
         * @param accountNo
         * @return
         */
        Integer checkRegister(@Param("userNo") String userNo, @Param("email") String email, @Param("phone")String phone);
    

    mapper文件:

    	<select id="checkRegister" resultType="java.lang.Integer" >
          select
           1
          from t_trade_user
          where userNo = #{userNo} or email = #{email} or phone = #{phone}
          limit 1
        </select>
    
  • 获取公司信息接口

    作公司判断, 并冗余公司相关信息,IStockUserDao:

        /**
         * 根据公司ID获取公司对象信息
         * @param id
         * @return
         */
        CompanyVo  getCompanyVoById(@Param("id")Long id);
    

    CompanyVo定义:

    @Data
    public class CompanyVo {
    
        /**
         * 公司ID
         */
        private Long id;
        
        /**
         * 公司名称
         */
        private String companyName;
    
        /**
         * 机构类型
         */
        private String institutionTypeId;
    
        /**
         * 联系人
         */
        private String contactUser;
    
        /**
         * 联系电话
         */
        private String contactPhone;
    
        /**
         * 管理员账号
         */
        private String adminUser;
    
        /**
         * 机构ID
         */
        private String institutionId;
    
    }
    
    

    mapper文件:

    <select id="getCompanyVoById" resultType="com.itcast.trade.stock.user.vo.CompanyVo" >
         select
         u.id, u.companyName, u.institutionTypeId, u.contactUser, u.contactPhone,
         u.adminUser, u.status, u.createUser, u.createTime, t.id as institutionId
         from t_company u left join t_institution t on u.id = t.detailInstitutionId
         where u.id = #{id}
         limit 1
    </select>
    

3.6 全局序列号生成方案

  1. 全局序列可能在多出需使用, 我们把它的实现放在stock-common-system公用组件下面。

  2. 这种方式适合并发量不大的业务场景, 对于高频度的交易场景,要结合缓存适用, 如果不需要保持全局序号,可以采用雪花算法。

  3. 数据层, 定义接口:

    ITradeGlobalConfigDao:

        /**
         * 根据编号获取序列ID
         * @param code
         * @return
         */
        int getNextId(TradeSeq record);
    
  4. Mapper文件, 通过update 来获取自增ID, 注意属性名称和类型不要写错:

        <update id="getNextId" keyColumn="nextId" keyProperty="nextId"  parameterType="com.itcast.trade.stock.entity.system.TradeSeq">
            update t_seq set nextId = last_insert_id(nextId + 1) where code =#{code};
            <selectKey resultType="long" keyProperty="nextId" keyColumn="nextId" order="AFTER">
                SELECT LAST_INSERT_ID()
            </selectKey>
        </update>
    
  5. Service层接口实现:

    TradeGlobalConfigServiceImpl增加:

        /**
         * 获取指定序列增长ID
         * @param code
         * @return
         */
        public Long getNextSeqId(String code) {
            TradeSeq seq = new TradeSeq();
            seq.setCode(code);
            tradeGlobalConfigDao.getNextId(seq);
            return seq.getNextId();
    
        }
    

    需要使用的地方, 通过ITradeGlobalConfigService服务类调用即可。

  6. Service层接口实现:

    StockUserServiceImpl增加:

       /**
         * 生成用户编号
         * @return
         */
        private String generateUserNo() {
    
            // 获取用户账号
            Long nextUserNo = tradeGlobalConfigService.getNextSeqId(GlobalSeq.USER_NO);
            log.info(" get the next userNo : " + nextUserNo);
            // 其中0表示补零, 后面标识长度
            return String.format("%08d", nextUserNo);
        }
    

3.7 服务层

  1. 增加用户注册接口

在这里插入图片描述

StockUserServiceImpl代码实现:

    /**
     * 用户注册
     * @param tradeUser
     * @return
     * @throws ComponentException
     */
    @Transactional(rollbackFor = Exception.class)
    public TradeUser userRegister(TradeUser tradeUser) throws ComponentException {

        TradeUser newTradeUser = new TradeUser();
        // 1. 判断用户信息是否已经注册
        Integer checkResult = stockUserDao.checkRegister(tradeUserVo.getUserNo(), tradeUserVo.getEmail(), tradeUserVo.getPhone());
        if(null != checkResult && checkResult > 0 ) {
            throw new BusinessException(ApplicationErrorCodeEnum.USER_EXISTS);
        }

        // 2. 对公司信息作校验
        CompanyVo companyVo = stockUserDao.getCompanyVoById(tradeUserVo.getCompanyId());
        if(null == companyVo) {
            throw new BusinessException(ApplicationErrorCodeEnum.USER_COMPANY_NOT_FOUND);
        }

        // 3. 构造生成用户信息
        BeanUtils.copyProperties(tradeUserVo, newTradeUser);
        newTradeUser.setUserNo(generateUserNo());
        newTradeUser.setUserPwd(EncryptUtil.encryptSigned(tradeUserVo.getUserPwd()));


        // 4. 完善冗余信息
        newTradeUser.setInstitutionId(companyVo.getInstitutionId());
        newTradeUser.setInstitutionTypeId(companyVo.getInstitutionTypeId());
        newTradeUser.setCompanyName(companyVo.getCompanyName());
        stockUserDao.insert(newTradeUser);

        // 5. 注册即开户, 生成交易账户信息
        TradeGlobalConfig tradeGlobalConfig = tradeGlobalConfigService.getTradeGlobalConfigByCode(GlobalConfig.REG_OPEN_ACCOUNT);
        if(null != tradeGlobalConfig && GlobalConfig.VALUE_TRUE.equals(tradeGlobalConfig.getValue())) {
            // 如果存在该项配置, 并且值为Y, 则代表注册即开户
            // 交易账户信息的生成
            TradeAccount tradeAccount = new TradeAccount();
            tradeAccount.setAccountNo(generateUserNo());
            // 先设置为系统的默认账户组, 后面可通过后台转组去划分到不同的组别
            tradeAccount.setTradeGroupId(1L);
            tradeAccount.setStatus(GlobalConfig.STATUS_VALID);
            // 保存交易账户
            tradeAccountDao.insert(tradeAccount);

        }

        //TODO  发送邮件/短信通知 (后面章节阿里云邮件发送功能时再做实现)
        return newTradeUser;
    }
    

  • 先做用户检查, 支持手机号和邮箱方式注册, 要做重复判断。

  • 这里用到了全局唯一序列生成用户编号, 通过Mysql表方式实现,结合last_insert_id函数, 只影响Connection, 防止并发问题。

  • 校验通过之后, 保存用户信息。

  • 邮件通知待后面邮件功能实现完成之后再加上。

3.8 接入层实现

新建StockUserOpenController类:

@RestController()
@RequestMapping("/open")
@Log4j2
public class StockUserOpenController {

    @Autowired
    private IStockUserService stockUserService;

    /**
     * 用户注册接口
     * @return
     */
    @RequestMapping("/register")
    public ApiRespResult register(@Valid TradeUserVo tradeUser) {

        ApiRespResult result = null;
        try {
            stockUserService.userRegister(tradeUser);
            result = ApiRespResult.success(tradeUser);
        }catch(ComponentException e) {
            log.error(e.getMessage(), e);
            result = ApiRespResult.error(e.geterrorCodeEnum());
        }catch(BusinessException e) {
            log.error(e.getMessage(), e);
            result = ApiRespResult.error(e.geterrorCodeEnum());
        }catch(Exception e) {
            log.error(e.getMessage(), e);
            result = ApiRespResult.sysError(e.getMessage());
        }

        return result;
    }

}


通过调用stockUserService的userRegister处理登陆逻辑。

3.9 添加自动化校验

作为一个健壮的后台服务, 需要完善的校验机制, 但接口繁多, 通过自动化校验组件能让我们快速实现。

  1. 添加依赖, 采用较新的稳定版本6.0.16.Final:

        <!-- 自动化校验 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.16.Final</version>
        </dependency>
    
  2. 在Controller中在需要校验的参数中, 增加校验注解
    在这里插入图片描述

  3. 在参数体中增加校验规则:

        /**
         * 用户名称
         */
        @Size(min = 1, max = 32,message = "姓名长度必须为1到32")
        private String name;
    
        /**
         * 用户密码
         */
        @Size(min = 1, max = 32,message = "密码长度必须为1到32")
        private String userPwd;
    
    	/**
         * 手机号
         */
        @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$", message="手机号格式错误")
    	private String phone;
    
    
  4. 自定义校验错误信息

默认返回的校验提示信息繁杂, 为统一接口数据格式, 需要对自动化校验错误做层封装。

增加校验异常拦截器ParamValidExceptionHandler:


@ControllerAdvice
public class ParamValidExceptionHandler {

     /**
     * 捕获所有校验异常信息,  进行封装, 返回给客户端, 捕获的是BindException
     * @param ex
     * @return
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ApiRespResult handlerException(BindException ex) {
        // 获取所有校验错误提示
        StringBuffer stringBuffer = new StringBuffer();
        List<FieldError> errors = ex.getBindingResult().getFieldErrors();
        // 遍历属性校验结果集
        for(FieldError fieldError : errors) {
            stringBuffer.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage());
        }
        // 封装校验异常返回信息
        ApiRespResult errorWebResult = ApiRespResult.validError(stringBuffer.toString());
        return errorWebResult;
    }


    /**
  * 拦截约束性异常的处理, 比如@NotBlank, 非空的必要性约束等, 捕获的是ConstraintViolationException
     * @param ex
  * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiRespResult handlerConstraintException(ConstraintViolationException ex) {
        // 获取所有校验错误提示
        StringBuffer stringBuffer = new StringBuffer();
        Set<ConstraintViolation<?>> errors = ex.getConstraintViolations();
        // 遍历属性校验结果集
        for(ConstraintViolation<?> constraintViolation : errors) {
            stringBuffer.append(constraintViolation.getMessage());
        }
        // 封装校验异常返回信息
        ApiRespResult errorWebResult = ApiRespResult.validError(stringBuffer.toString());
        return errorWebResult;
    }
}

这样就完成了自动化校验的处理。

附上一些常用的校验规则的配置:

限制说明
@Null限制只能为null
@NotNull限制必须不为null
@AssertFalse限制必须为false
@AssertTrue限制必须为true
@DecimalMax(value)限制必须为一个不大于指定值的数字
@DecimalMin(value)限制必须为一个不小于指定值的数字
@Digits(integer,fraction)限制必须为一个小数,且整数部分的位数不能超过
@Future限制必须是一个将来的日期
@Max(value)限制必须为一个不大于指定值的数字
@Min(value)限制必须为一个不小于指定值的数字
@Past限制必须是一个过去的日期
@Pattern(value)限制必须符合指定的正则表达式
@Size(max,min)限制字符长度必须在min到max之间
@Past验证注解的元素值(日期类型)比当前时间早
@NotEmpty验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空
@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格

FAQ补充:

如果使用约束性异常@NotBlank不生效的情况, 要注意在使用的类上加上@Validated

public ApiRespResult userLogin(@NotBlank(message = "用户编号不能为空!") String userNo, String userPwd)

对应的类为StockUserController, 加上:

@Validated
public class StockUserController extends BaseController

3.10 启动服务

  1. 启动必要的依赖服务
    启动用户服务, 认证服务可以不用启动, 因为注册功能是开放的, 用户注册成功, 拥有账号之后才能进行认证。
    这也是新增的用户注册接口, 前缀路径为/open的原因, 便于我们区分管理。
    检查认证配置ResourceSecurityConfigurer, 不要把包含/open前缀的请求加入验证:
    在这里插入图片描述

3.11 功能验证

  1. 请求注册接口, 地址: 127.0.0.1:10681/open/register

在这里插入图片描述

注册成功, 返回用户信息。

  1. 校验功能验证(逻辑校验与自动化校验)
  • 重复注册请求

在这里插入图片描述

 提示用户已存在。
  • 密码为空, 发出请求

在这里插入图片描述

 出现我们预期封装的错误提示。
  • 用户名和密码都为空, 发出请求, 能够将所有错误都提示出来

在这里插入图片描述

4. 总结

  • 用户注册功能虽然简单, 但想要做得完善, 更为健全, 需要把每一步都做好, 通过全局序列号生成, 能够保障分布式服务的正常运行, 加入自动化校验, 能够避免非法请求, 不必要的异常或错误数据。

  • 任何一个业务功能不要着急去实现, 先了解其业务流程, 做好底层数据库结构设计, 考虑需要提供哪些主要接口, 再从数据层到服务层编码实现, 形成良好的思路, 编写的代码也会层次清晰, 避免反复调整修改, 功能实现起来自然水到渠成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/109941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

flstudio21版本有什么新功能及免费新插件

全能数字音乐工作站&#xff08;DAW&#xff09;编曲、剪辑、录音、混音&#xff0c;23余年的技术积淀和实力研发&#xff0c;FL Studio已经从电音领域破圈&#xff0c;成功蜕变为瞩目的全能DAW&#xff0c;把电脑变成全功能音乐工作室&#xff0c;接下来我们会为您一一展示 2…

Prometheus Operator实战—— Prometheus、Alertmanager、Grafana 监控RockectMq

1. RocketMQ 介绍 RocketMQ 是一个分布式消息和流数据平台&#xff0c;具有低延迟、高性能、高可靠性、万亿级容量和灵活的可扩展性。简单的来说&#xff0c;它由 Broker 服务器和客户端两部分组成&#xff0c;其中客户端一个是消息发布者客户端(Producer)&#xff0c;它负责向…

硬盘恢复工具软件哪个好?分享这些硬盘数据恢复工具软件

您刚刚删除了一些非常重要的文件&#xff01; 不要惊慌……您仍然有很大的机会可以以很少甚至免费的方式取回它们。 我们正在深入研究当今最好的硬盘恢复软件。 我们认为有一个明显的赢家&#xff0c;但我们提供了一些其他选项&#xff0c;以防您需要更高级的功能或使用不同…

四、网络层(三)IPv4

目录 3.1 IPv4地址 3.1.1分类编址 3.1.2子网划分与子网掩码 3.1.3无分类编址CIDR 3.1.4网络地址转换&#xff08;NAT&#xff09; 3.2 IPv4分组 3.2.1 IP分组&#xff08;IP数据报&#xff09;的格式 3.2.2 IP数据报分片 3.3 地址解析协议&#xff08;ARP&am…

计讯物联二次供水水池泵站监测方案,从根本上保障居民饮用水安全

水质污染、设施故障率高、供水压力、安防缺失、故障反馈周期长等城市高楼大厦高层供水问题层出不穷&#xff0c;给二次供水安全带来隐患和威胁。为确保高层住宅安全稳定地进行二次供水&#xff0c;计讯物联利用新一代物联网技术、信息技术、云计算、大数据、数字孪生技术等先进…

ESP32中micro-ROS与ROS2通信(点亮esp32指示灯)

前言 micro-ROS&#xff0c;是基于ROS2进行优化的一套轻量级ROS系统&#xff0c;它提供了完全部署的ROS 2生态系统的大多数吸引人的工具和功能&#xff0c;并具有入式和低资源设备的卓越能力&#xff0c;可以运行在MCU硬件平台。 传统上&#xff0c;即使机器人包含许多ROS&am…

视频号直播间首次突破1万人

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 12月21直播结束后&#xff0c;看了下直播数据&#xff0c;竟然有100多人在线时长达到了1小时以上。看来直播间用户的粘性越来越强了&#xff0c;固定用户越来越多。个人做直播已经2年了&#xff0c…

聚观早报 | 马斯克或将卸任推特CEO;小米内部人士回应年底裁员

今日要闻&#xff1a;马斯克或将卸任推特CEO&#xff1b;小米内部人士回应年底裁员&#xff1b;微软或将于明年收购Netflix&#xff1b;奥迪正逐步淘汰燃油车&#xff1b;支付宝开通健康防疫专区马斯克或将卸任推特CEO 自今年 10 月份入主推特以来&#xff0c;马斯克一系列大刀…

[洛谷]【深基16.例3】二叉树深度

一、题目描述 题目描述 有一个 n(n≤106)n(n \le 10^6)n(n≤106) 个结点的二叉树。给出每个结点的两个子结点编号&#xff08;均不超过 nnn&#xff09;&#xff0c;建立一棵二叉树&#xff08;根节点的编号为 111&#xff09;&#xff0c;如果是叶子结点&#xff0c;则输入 …

实测 | 海纳百川,华为OceanStor Pacific分布式存储为多元算力应用带来更优选择...

被称为开眼看世界的林则徐&#xff0c;在自己的书房中写了这样一副对联&#xff0c;以做自勉&#xff1a;海纳百川&#xff0c;有容乃大&#xff0c;壁立千仞&#xff0c;无欲则刚。包容的胸怀是我们做成很多事情的根基&#xff0c;也是一项产业战略、科技事业发展的关键。近几…

数据开源 | Magic Data开源基于ChatGPT的可扩展的对话数据集

在过去的一月里&#xff0c;人工智能领域中最火的话题莫过"ChatGPT"。ChatGPT是OpenAI于11月30日发布最新作品聊天机器人&#xff0c;开放公众免费测试。聊天机器人是一种软件应用程序&#xff0c;根据用户的提问做出回应、模仿人类的对话方式。目前&#xff0c;Chat…

做好程序中的axios错误提示

今天外包组的项目客户反馈老是出错&#xff0c;一看页面卡在加载数据过程中&#xff0c;前后台分离之后&#xff0c;页面变得很奇怪&#xff0c;脸面都出来了&#xff0c;就是没有具体值。 初级程序员在很长一段时间要经历一个升级过程&#xff0c;写的程序首先是写画面&#…

散户如何进行开展量化股票交易的?

散户如何进行开展量化股票交易的&#xff1f;也就是投资者交易的条件&#xff0c;达到了投资者设定的条件时候&#xff0c;系统接口就会自动交易&#xff0c;下面来看看具体的流程&#xff1a; 依据个股的历史记录&#xff0c;进行多因子选股&#xff0c;比如&#xff0c;把市…

Rasa 基于知识库的问答 音乐百科机器人

文章目录1. 使用 ActionQueryKnowledgeBase创建知识库NLU数据2. 音乐机器人nlu.ymlstories.ymlrules.ymldomain.ymlconfig.ymlendpoints.ymldata.json自定义动作 actions.py测试使用Neo4jlearn from https://github.com/Chinese-NLP-book/rasa_chinese_book_code 机器人返回了…

从“小螺栓血案”谈装配体模型连接螺栓6个正确的处理方法

经调查发现&#xff1a;“江苏启安建设工程有限公司提供了支吊架膨胀螺栓计算书&#xff0c;但计算书上无企业相关人员签字&#xff0c;未经监理单位审核同意&#xff0c;未报施工总包单位&#xff0c;未经设计单位审定&#xff1b;计算书认为支吊架应采用直径为12mm 的膨胀螺栓…

成为全栈程序员太难了?这个低代码高效率的报表工具绝对不能错过

现在程序员有个很奇怪的归宿&#xff0c;就是都在主动或被动地成为全栈程序员。前端被要求写后端的代码&#xff0c;后端被要求能看懂前端&#xff0c;美名其曰加速成长或为老板省钱&#xff01; 但一个人能做到全栈真的很难&#xff01;就拿报表开发而言&#xff0c;学习整个…

朴素贝叶斯

概要 前文介绍了贝叶斯公式基础以及在统计领域的基本应用贝叶斯基础_zhanglehes的博客-CSDN博客&#xff0c;本文将介绍它的一种新的转换形式&#xff0c;以及在机器分类领域的应用。 分类的数学描述 朴素贝叶斯公式推理 贝叶斯公式 在分类领域&#xff0c;将其改写如下 我们…

在conda虚拟环境中安装OpenCv并在pycharm中使用

目录 一. 在下面的网站中下载OpenCV文件 二. 在虚拟环境中使用pip安装该文件 三、官网下载OpenCV源代码&#xff08;后续使用&#xff09; 四、pycharm中打开该虚拟环境 五、安装numpy和matplotlib 六、pycharm找不到cv2模块解决&#xff1a; 七、在pyhcharm中使用cv不自…

在服务器安装jupyter并在本地访问

一、安装 1.1安装jupyter notebook pip install jupyter1.2安装jupyter lab pip install jupyterlab # 中文界面包 pip install jupyterlab-language-pack-zh-CN二、本地使用 我们在远程登录Linux服务器时&#xff0c;经常希望在本地浏览器端打开jupyter notebook&#xff…

需求的收集,筛选和排序

对需求的把握是否准确&#xff0c;很大程度上决定了产品的成与败&#xff0c;需求分析对于产品经理是必须要掌握的技能&#xff0c;接下来聊下需求收集的目标和方式。 一、收集渠道&#xff1a; 1、用户反馈&#xff1a; 用户提出的反馈有可能也是我们没有意识到的问题&#…