项目day02(今日指数之登录功能)

今日目标


1. 完善基于前后端分用户验证码登录功能;
2. 理解验证码生成流程,并使用postman测试;
3. 理解并实现国内大盘数据展示功能;
4. 理解并实现国内板块数据展示功能;
5. 理解后端接口调试和前后端联调的概念;

1.验证码登录功能


1.1 验证码功能分析

1)前后端分离架构的session问题

单体架构session实现验证码流程:

解释:

如果前端后端都在一个工程下 统一部暑 首先访问验证码接口 将生成校验码保存在后台session下

每个session都有一个sessionid 也就是说验证码在后台保存了一份 然后将sessionid放到cookie中

响应给前端 把验证码也给前端 这样子在前端我们能够拿到验证码 也可以拿到sessionid,这样子的话 当我们输入验证码,点击登录后 就可以把sessionid和验证码带回去给后台 因为cookie来自于服务器,不存在跨域问题问题 所以服务端接收到请求之后 就可以从cookie中解析到sessionid sessionid获取到之后 根据K:V值就可以获取验证码 然后在与后台根据sessionid获取保存的验证码进行对比

但是当前我们的项目存在跨域问题 cookie不同源 前端的cookie是无法发送到服务器后端 所以后端拿不到前端的sessionid

当前我们的项目采用前后端分离的技术架构,因为前后端请求存在跨域问题,会导致请求无法携带和服务器对应的cookie,导致session失效,且后续服务端也会做集群方案部署,整体来看使用session方案带来的扩展和维护成本是比较高的!

2)验证码逻辑分析

我们可使用分布式缓存redis模拟session机制,实现验证码的生成和校验功能,核心流程如下:

解释:

第一次请求:

刚开始时 第一次 页面一加载 接口主动访问, 访问地址是api/captcha

请求经过代理之后 就来到了服务器后端 后端先生成验证码 然后把验证码保存在redis下 这样子的话 哪怕到时做集群 大家都访问的是共同的服务 都能做到共享 也就是由原来要放到session中的缓存数据 现在把他们放在公共的redis下(公共的缓存区域)

然后在把后台生成的验证码响应给前端 前端就能够得到后台随机生成的验证码 这样用户就可以存入这个验证码 之后 我们在点击登录 就可以发起了第二次请求

第二次请求

携带用户输入的验证码登录 地址是/api/login/ 这样就把前端用户输入的验证码转入后台 后台在从redis获取到验证码和前端传入的验证码进行比较 如果不是 就是销毁 如果是 就说明验证成功

思考:存储redis中验证码的key又是什么呢?

模拟sessionId ,我们可以借助工具类生成全局唯一ID;

3)验证码生成接口说明
请求路径:/api/captcha
请求参数:无
响应数据格式:
    {
        "code": 1,
        "data": {
            "code": "5411", //响应的验证码
            "rkey": "1479063316897845248" //保存在redis中验证码对应的key,模拟sessioinId
        }
    }

1.2.redis环境集成

前后端分离后,后台session无法共享使用,所以我们可以把验证码数据存入redis中,所以接下来,backend项目中先引 入redis的依赖:

<!--redis场景依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis创建连接池,默认不会创建连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!--apache工具包,提供验证随机码工具类-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

yml配置redis:

spring:
  # 配置缓存
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0 #Redis数据库索引(默认为0)
    lettuce:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 # 连接池中的最大空闲连接
        min-idle: 1  # 连接池中的最小空闲连接
    timeout: PT10S # 连接超时时间(毫秒)

备注:因为我这里面部暑在本地 所以 host连接的是本地 如果在部暑到linux 那么就要写linux的ip

添加RedisConfig redis Key序列化配置文件

package com.itheima.stock.config;

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置类
 */

@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

        //默认的Key序列化器为:JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        redisTemplate.setConnectionFactory(connectionFactory);

        return redisTemplate;
    }

注意:这个配置文件和视频里面的可能不行 因为我没有视频里面的配置文件

测试redis基础环境:

package com.itheima.stock;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
public class TestRedisDemo {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Test
    public void test01(){
        //存入值
        redisTemplate.opsForValue().set("myname","zhangsan");
        //获取值
        String myname = redisTemplate.opsForValue().get("myname");
        System.out.println(myname);
    }
}

我的

视频的

清除所有的Key:FLUSHALL

清除所有key

测试结果

获取到k

1.3 验证码功能实现

1)配置id生成器

导入id生成器工具类:

在CommonConfig类里面配置id

package com.itheima.stock.config;

import com.itheima.stock.utils.IdWorker;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/*定义公共配置类*/

@Configuration
public class CommonConfig {

    /**
     * 配置基于雪花算法生成全局唯一id
     * 参与运算的参数:时间戳+机房id+机器id+序列号
     * 保证id唯一
     * 配置id生成器bean
     * @return
     */
    @Bean
    public IdWorker idWorker(){
        //指定当前为1号机房的2号机器生成
        return new IdWorker(2L,1L);
    }


    /**
     * 密码加密器   定义密码加密器和解密器 bean
     * BCryptPasswordEncoder方法采用SHA-256对密码进行加密
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

2)定义web接口

在UserController接口定义访问方法:

视频

我的


@RestController
@RequestMapping("/api")
public class UserController {

  /*  @GetMapping("/test")
    public String getName(){
        return "itheima";
    }*/

    @Autowired
    private UserService userService;

    /**
     * 用户登录功能实现
     * @param vo
     * @return
     */
    @PostMapping("/login")
    public R<LoginRespVo> login(@RequestBody LoginReqVo vo){

        return userService.login(vo);
    }


    /**
     * 生成登录校验码
     * 生成验证码
     *  map结构:
     *      code: xxx,
     *      rkey: xxx
     * @return
     */
    @GetMapping("/captcha")
    public R<Map> generateCaptcha(){
        return this.userService.generateCaptcha();
    }
}

3)定义生成验证码服务

在UserService服务接口:

在UserController类中的generateCaptcha中Alt+回车跳转到UserService接口


/*定义用户服务接口*/

public interface UserService {

          /*用户登录功能*/

    R<LoginRespVo> login(LoginReqVo vo);

       /*生成登录校验码*/

    R<Map> generateCaptcha();

}

方法实现:

@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private RedisTemplate redisTemplate;

    /*
    * 生成登录验证码
    * */
    @Override
    public R<Map> generateCaptcha() {

        //1.生成4位数字验证码
        String checkCode = RandomStringUtils.randomNumeric(4);
        //2.获取全局唯一id
        //生成一个类似sessionid的id作为key,然后校验码作为value保存在redis下
        String rkey=String.valueOf(idWorker.nextId());
        //验证码存入redis中,并设置有效期1分钟
        redisTemplate.opsForValue().set(rkey,checkCode,60, TimeUnit.SECONDS);
        //3.组装数据  组装响应的map对象
        HashMap<String, String> map = new HashMap<>();
        map.put("rkey",rkey);
        map.put("code",checkCode);
        return R.ok(map);
    }
}
理解代码:
第一步  生成一个随机码  调用第三方工具类 RandomStringUtils
第二步  用雪花算法保证全局唯一 生成一个Id
第三步  让sessionid/rkey做为key  checkCode 验证码作为value 保存在redis下面
第四步   然后对应的rkey/ sessionid和验证码checkCode发给前台

打断点

打开Postman

为了测试 我们把存活时间修改成300秒 5分钟 正常生活中是一分钟

F8

F8

接下来我们把保存到redis下面

F8

3次F8

F9

接下去 我们联调一下 注意打开这个前端浏览器 时 要去启动前端 如何启动看第一天

刷新

刷新

F8

接下来 我们来看一下前端代码 理解 一下rkey放在哪里

页面只要一加载 访问验证码的动作会初步触发 而这个是在vue的钩子函数下触发的

为了使用方便 我们用idea打开前端代码

进入captcha()方法里面

在后端只要拿到from 就可以拿到rkey

1.4 完善验证码登录功能

看下前端代码

一点击登录 loginSubmit('form')就可以触发这个事件的完成

会传进来一个名称loginSubmit(name)

根据这个名称获取from对象 form对象下有username password code

在推送的时候应该要有rkey这个属性 但是我们之前封装的vo并没有rkey这个属性

所以我们要添加一个rkey这个属性

1)完善登录请求VO

LoginReqVo添加rkey属性:

package com.itheima.stock.vo.req;

import lombok.Data;

/*用户登录请求vo*/

@Data
public class LoginReqVo {

    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 前端发送的验证码
     */
    private String code;

    /**
     * 保存redis随机码的key
     * 前端发送的sessionid
     */
    private String rkey;  
}

以后前端只要一登录 rkey就会带过去 带到后台的login中 @RequestBody一反序列化 LoginReqVo下 就能获取元素

获取到元素之后 把对象vo放到服务层进行逻辑处理

2)完善登录验证码逻辑

添加校验码校验功能:

第一种写法

@Service("userService")
public class UserServiceImpl implements UserService {
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public R<LoginRespVo> login(LoginReqVo vo) {
        //1.判断vo是否存在 或者用户名是否存在或都密码是否存在
        if (vo == null || Strings.isNullOrEmpty(vo.getUsername()) || Strings.isNullOrEmpty(vo.getPassword())
                || Strings.isNullOrEmpty(vo.getCode()) || Strings.isNullOrEmpty(vo.getRkey())) {
                 //快速淘汰校验码,合理利用redis的内存空间
                //redis清除key
                 redisTemplate.delete(vo.getRkey());
                 return R.error(ResponseCode.DATA_ERROR.getMessage());
        }
        //校验验证码
        //获取redis中rey对应的验证码
        //获取redis中rkey对应的code验证码
        String redisCode = (String) redisTemplate.opsForValue().get(vo.getRkey());
         //比对
        if (redisCode==null || !redisCode.equals(vo.getCode())) {
               return R.error(ResponseCode.DATA_ERROR.getMessage());
        }
        //快速淘汰校验码,合理利用redis的内存空间
        //redis清除key
        redisTemplate.delete(vo.getRkey());

        //2. 根据用户名判断用户是否存在
        SysUser userInfo = sysUserMapper.findUserInfoByUserName(vo.getUsername());
        if (userInfo == null) {
            return R.error(ResponseCode.DATA_ERROR.getMessage());
        }
          //3.判断密码,不匹配
        if (!passwordEncoder.matches(vo.getPassword(), userInfo.getPassword())) {
            return R.error(ResponseCode.SYSTEM_PASSWORD_ERROR.getMessage());
        }
        //4.属性赋值  两个类之间属性名称一致
        LoginRespVo respVo = new LoginRespVo();
        BeanUtils.copyProperties(userInfo,respVo);
        return R.ok(respVo);
    }
    /*
    * 生成登录验证码
    * */
    @Override
    public R<Map> generateCaptcha() {

        //1.生成4位数字验证码
        String checkCode = RandomStringUtils.randomNumeric(4);
        //2.获取全局唯一id
        //生成一个类似sessionid的id作为key,然后校验码作为value保存在redis下
        String rkey=String.valueOf(idWorker.nextId());
        //验证码存入redis中,并设置有效期1分钟
        redisTemplate.opsForValue().set(rkey,checkCode,300, TimeUnit.SECONDS);
        //3.组装数据  组装响应的map对象
        HashMap<String, String> map = new HashMap<>();
        map.put("rkey",rkey);
        map.put("code",checkCode);
        return R.ok(map);
    }
    
}

第二种写法

    /**
     * 用户登录服务实现
     * @param vo
     * @return
     */
    @Override
    public R<LoginRespVo> login(LoginReqVo vo) {
        if (vo==null || Strings.isNullOrEmpty(vo.getUsername())
                || Strings.isNullOrEmpty(vo.getPassword()) || Strings.isNullOrEmpty(vo.getRkey())){
            return R.error(ResponseCode.DATA_ERROR.getMessage());
        }
        //验证码校验
        //获取redis中rkey对应的code验证码
        String rCode = (String) redisTemplate.opsForValue().get(vo.getRkey());

        //校验
        if (Strings.isNullOrEmpty(rCode) || !rCode.equals(vo.getCode())) {
            return R.error(ResponseCode.DATA_ERROR.getMessage());
        }
        //redis清除key
        redisTemplate.delete(vo.getRkey());
        //根据用户名查询用户信息
        SysUser user=this.sysUserMapper.findByUserName(vo.getUsername());
        //判断用户是否存在,存在则密码校验比对
        if (user==null || !passwordEncoder.matches(vo.getPassword(),user.getPassword())){
            return R.error(ResponseCode.SYSTEM_PASSWORD_ERROR.getMessage());
        }
        //组装登录成功数据
        LoginRespVo respVo = new LoginRespVo();
        BeanUtils.copyProperties(user,respVo);
        return  R.ok(respVo);
    }

打断点

2次F8

F8

发现是个null

先把他放行 F9

可能原因是时间太快了(过期了) 所以为了方便演示 我们把时间调大点 我这里面直接修改成30分钟

重启

2次F8

F8

3次F8

5次F8后放行

3)登录测试联调

页面登录效果:

2.国内大盘指数功能


2.1国内大盘指数业务分析

1)页面原型效果

大盘展示需要的字段:

国内大盘数据包含:

大盘代码、名称、前收盘价、开盘价、最新价、涨幅、总金额、总手、当前日期
2)相关表结构分析

大盘指数包含国内和国外的大盘数据,目前我们先完成国内大盘信数据的展示功能;

股票大盘数据详情表(stock_market_index_info)设计如下:

相关的开盘与收盘流水表(stock_market_log_price)设计如下:

开盘和收盘流水表与股票大盘数据详情表通过market_code进行业务关联,同时一个大盘每天只产生一条开盘与收盘流水数据,该数据,后期通过定时任务统计获取;

显然后去上述数据,需要大盘表和价格流水表的联合查询;

3)国内大盘数据注意事项
1.如果当前时间没有最新的大盘数据,则显示最近有效的大盘数据信息;
  比如:今天是周1上午九点,则显示上周五收盘时的大盘数据信息;☹☹☹
2.当前大盘的数据采集频率为一分钟一次;

工程直接导入日期工具类:今日指数\day02\资料\date工具类\DateTimeUtil.java

打开类结构路径 Alt+7

如果出现以下情况 说明 没有编译

在这里面看看有没有编译

查看以下内容 发现并没有Util

先判断是否在工作日

F8

Shift+F8

F8

可以用Debug慢慢调理(先放着)

jode和工具类使用参考:day02\资料\date工具类\TestJodeDate.java

获取joda提供的date

获取java提供的date

理论上上可以直接

或是用

主要学习以下的

第一个Test

 DateTime.now().withDate(2022, 1, 9);  2022年1月9号不变 时分秒为当前系统的时间 因为时分秒没有设置 
DateTime.now().withDate(2022, 1, 10).withHourOfDay(9).withMinuteOfHour(0);
2022年1月10号9点02发不变  秒发生变化 为系统时间

第二个Test

        //获取jode下的当前时间
        DateTime now = DateTime.now();
        //日期后退指定的时间   当前的时期向后加一天
        DateTime plusDay = now.plusDays(1);
        System.out.println(plusDay);
//前推指定的时间
DateTime now = DateTime.now();      
DateTime preDate = now.minusDays(5);
System.out.println(preDate);

4)国内大盘指数接口说明
请求路径:/api/quot/index/all
请求方式:GET
参数:无

响应数据格式:

{
    "code": 1,
    "data": [
        {
            "tradeAmt": 235158296,//交易量
            "preClosePrice": 78.9,//前收盘价格
            "code": "s_sz399001",//大盘编码
            "name": "深证成指",//大盘名称
            "curDate": "202112261056",// 当前日期
            "openPrice": 79.2,//开盘价
            "tradeVol": 32434490,//交易金额
            "upDown": -0.89,//涨幅
            "tradePrice": -131.52//当前价格
        },
        {
            "tradeAmt": 1627113,
            "code": "s_sh000001",
            "name": "上证指数",
            "curDate": "202112261056",
            "tradeVol": 21549808,
            "upDown": -0.56,
            "tradePrice": -20.26
        }
    ]
}

cur_time(当前时间)----curDate(当前日期)

mark_name(指数名称)---

cur_point(当前点数)---

current_price(当前价格)---preClosePrice(前收盘价) openPrice(开盘价)

updown_rate(涨跌率)--

trade_account(成交量(多少手))--tradeAmt(交易量) tradeVol(交易金额)

trade_volume(成交额(万元))

5)DO封装

注意:直接导入day02\资料\domain\InnerMarketDomain.java

2.2 国内大盘指数功能实现

先分析sql

# 获取最新的国内大盘的数据信息 上证 深证---》s_sh000001、s_sz399001
# 最新:最近最新交易产生的数据 -- 2022-01-03 11:15:00
# 大盘流水表与大盘价格日统计表没有必然的联系(两张独立的表)
# 1.先查询主表信息
select 
    smi.trade_account as tradeAmt,  
    smi.mark_Id as code, 
    smi.mark_name as name,
    date_format(smi.cur_time,'%Y%m%d%H%i') as curDate,
    smi.trade_volume as tradeVol,
    smi.updown_rate as upDown,
    smi.current_price as tradePrice
from stock_market_index_info as smi 
where smi.mark_Id in ('s_sh000001','s_sz399001') 
and smi.cur_time='2022-01-03 11:15:00';
# 2.然后获取主表信息数据后(数据被压缩)关联日统计表查询

select  
    tmp.*,
    sml.open_price as openPrice,
    sml.pre_close_price as preClosePrice 
from (select 
    smi.trade_account as tradeAmt,  
    smi.mark_Id as code, 
    smi.mark_name as name,
    date_format(smi.cur_time,'%Y%m%d%H%i') as curDate,
    smi.trade_volume as tradeVol,
    smi.updown_rate as upDown,
    smi.current_price as tradePrice
from stock_market_index_info as smi 
where smi.mark_Id in ('s_sh000001','s_sz399001') 
and smi.cur_time='2022-01-03 11:15:00') as tmp 
    left join stock_market_log_price as sml 
    on tmp.code=sml.market_code 
    and    date_format(sml.cur_date,'%Y%m%d')=date_format('2022-01-03 11:15:00','%Y%m%d')  

测试数据

1)常量数据封装

将大盘或外盘的常量数据配置在yml下:

`# 配置股票相关的参数
stock:
  inner: # A股
    - s_sh000001 # 上证ID
    - s_sz399001 #  深证ID
  outer: # 外盘
    - int_dji # 道琼斯
    - int_nasdaq # 纳斯达克
    - int_hangseng # 恒生
    - int_nikkei # 日经指数
    - b_TWSE # 台湾加权
    - b_FSSTI # 新加坡

实体类封装:


@ConfigurationProperties(prefix = "stock")
@Data
public class StockInfoConfig {
    //a股大盘ID集合
    private List<String> inner;
    //外盘ID集合
    private List<String> outer;
}

在main启动类上开启实体类配置:

@SpringBootApplication
//@MapperScan("com.itheima.stock.mapper")
@EnableConfigurationProperties(StockInfoConfig.class)  //开启配置初始化  加入IOC容器中
public class StockApp {
    public static void main(String[] args) {
        SpringApplication.run(StockApp.class, args);
    }
}
2)定义国内大盘web接口
@RestController
@RequestMapping("/api/quot")
public class StockController {

    @Autowired
    private StockService stockService;

    //其它省略.....
    /**
     * 获取国内最新大盘指数
     * @return
     */
    @GetMapping("/index/all")
    public R<List<InnerMarketDomain>> innerIndexAll(){
        return stockService.innerIndexAll();
    }
}
3)定义国内大盘数据服务

服务接口:

public interface StockService {
    //其它省略......
    /**
     * 获取国内大盘的实时数据
     * @return
     */
    R<List<InnerMarketDomain>> innerIndexAll();

}

服务接口实现:

`@Service("stockService")
public class StockServiceImpl implements StockService {

    @Autowired
    private StockBusinessMapper stockBusinessMapper;

    @Autowired
    private StockMarketIndexInfoMapper stockMarketIndexInfoMapper;

    @Autowired
    private StockInfoConfig stockInfoConfig;

    @Override
    public List<StockBusiness> getAllStockBusiness() {
        return stockBusinessMapper.findAll();
    }

    /**
     * 获取国内大盘的实时数据
     * @return
     */
    @Override
    public R<List<InnerMarketDomain>> innerIndexAll() {
        //1.获取国内大盘的id集合
        List<String> innerIds = stockInfoConfig.getInner();
        //2.获取最近最新的股票有效交易日
        Date lDate = DateTimeUtil.getLastDate4Stock(DateTime.now()).toDate();
        //mock数据
        String mockDate="20211226105600";//TODO后续大盘数据实时拉去,将该行注释掉 传入的日期秒必须为0
        lDate = DateTime.parse(mockDate, DateTimeFormat.forPattern("yyyyMMddHHmmss")).toDate();
        //3.调用mapper查询指定日期下对应的国内大盘数据
        List<InnerMarketDomain> maps=stockMarketIndexInfoMapper.selectByIdsAndDate(innerIds,lDate);
        //组装响应的额数据
        if (CollectionUtils.isEmpty(maps)) {
            return R.error(ResponseCode.NO_RESPONSE_DATA.getMessage());
        }
        return R.ok(maps);
    }
}
4)定义mapper接口方法和xml

mapper下定义接口方法和xml:

    /**
     * 根据注定的id集合和日期查询大盘数据
     * @param ids 大盘id集合
     * @param lastDate 对应日期
     * @return
     */
     List<InnerMarketDomain> selectByIdsAndDate(@Param("ids") List<String> ids, @Param("lastDate") Date lastDate);

XML绑定SQL分析:

SQL分析思路:

从设计角度看,大盘价格流水表和大盘实时流水表没有必然的练习,对于价格流水表仅仅记录当天的开盘价和前一个交易日的收盘价,也就是一个交易日仅产生一条数据,而大盘的实时流水则会产生N条数据,所以我们采取先查询大盘实时流水主表信息(将数据压扁),然后再关联价格日流水表进行查询。
# 步骤1:先查询指定时间点下大盘主表对应的数据
select * from stock_market_index_info as smi 
where smi.mark_Id in ('s_sh000001','s_sz399001') and     
 smi.cur_time='20211226105600';
#步骤2:将步骤1的结果作为一张表与log_price流水表左外连接查询,获取开盘和前收盘价格 
# 为什么左外?因为内连接只查询都存在的数据:
select tmp.trade_account as tradeAmt,tmp.mark_Id as code,tmp.mark_name as name,
date_format(tmp.cur_time,'%Y%m%d%H%i') as curDate,tmp.trade_volume as tradeVol,
tmp.updown_rate as upDown,tmp.current_price as tradePrice,sml.open_price as openPrice,
sml.pre_close_price preClosePrice
  from 
(
select * from stock_market_index_info as smi 
where smi.mark_Id in ('s_sh000001','s_sz399001') and     
 smi.cur_time='20211226105600'
) as tmp left join stock_market_log_price as sml on 
sml.market_code=tmp.mark_id and 
 date_format(sml.cur_date,'%Y%m%d')=date_format(tmp.cur_time,'%Y%m%d');

定义mapper接口绑定SQL:

    <select id="selectByIdsAndDate" resultType="com.itheima.stock.common.domain.InnerMarketDomain">
        SELECT
        tmp.mark_Id AS code,
        tmp.mark_name AS name,
        sml.pre_close_price AS preClosePrice,
        sml.open_price AS openPrice,
        tmp.current_price AS tradePrice,
        tmp.updown_rate AS upDown,
        tmp.trade_account AS tradeAmt,
        tmp.trade_volume AS tradeVol,
        DATE_FORMAT( tmp.cur_time, '%Y%m%d') AS curDate
        FROM
        (
        SELECT    *     FROM    stock_market_index_info AS smi
        WHERE smi.cur_time =#{lastDate}
        AND smi.mark_Id IN
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
        ) AS tmp
        LEFT JOIN stock_market_log_price AS sml ON tmp.mark_Id=sml.market_code
        AND DATE_FORMAT( sml.cur_date, '%Y%m%d' )= DATE_FORMAT(#{lastDate},'%Y%m%d' )
    </select>
5)接口测试

postman:http://127.0.0.1:8080/api/quot/index/all

页面最终显示效果:

3.板块指数功能实现


3.1 国内板块指数业务分析

1)功能原型效果
2)板块表数据分析

stock_block_rt_info板块表分析:

板块表涵盖了业务所需的所有字段数据。

3)国内板块接口说明
需求说明: 沪深两市板块分时行情数据查询,以交易时间和交易总金额降序查询,取前10条数据
请求URL: /api/quot/sector/all
请求方式: GET
请求参数: 无

接口响应数据格式:

{
    "code": 1,
    "data": [
        {
            "companyNum": 247,//公司数量
            "tradeAmt": 5065110316,//交易量
            "code": "new_dzxx",//板块编码
            "avgPrice": 14.571,//平均价格
            "name": "电子信息",//板块名称
            "curDate": "20211230",//当前日期
            "tradeVol": 60511659145,//交易总金额
            "updownRate": 0.196//涨幅
        },
        {
            "companyNum": 155,
            "tradeAmt": 4281655990,
            "code": "new_swzz",
            "avgPrice": 22.346,
            "name": "生物制药",
            "curDate": "20211230",
            "tradeVol": 52026876373,
            "updownRate": -0.068
        }
    ]
}
4)DO封装
package com.itheima.stock.pojo;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 股票板块详情信息表
 * @TableName stock_block_rt_info
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class StockBlockRtInfo implements Serializable {
    /**
     * 板块主键ID(业务无关)
     */
    private String id;

    /**
     * 表示,如:new_blhy-玻璃行业
     */
    private String label;

    /**
     * 板块名称
     */
    private String blockName;

    /**
     * 公司数量
     */
    private Integer companyNum;

    /**
     * 平均价格
     */
    private BigDecimal avgPrice;

    /**
     * 涨跌幅
     */
    private BigDecimal updownRate;

    /**
     * 交易量
     */
    private Long tradeAmount;

    /**
     * 交易金额
     */
    private BigDecimal tradeVolume;

    /**
     * 当前日期(精确到秒)
     */
    private Date curTime;

    private static final long serialVersionUID = 1L;
}

3.2 国内板块指数功能实现

1)定义板块web访问接口方法
    /**
     *需求说明: 沪深两市板块分时行情数据查询,以交易时间和交易总金额降序查询,取前10条数据
     * @return
     */
    @GetMapping("/sector/all")
    public R<List<StockBlockRtInfo>> sectorAll(){
        return stockService.sectorAllLimit();
    }
2)定义服务方法和实现

服务接口方法:

    /**
     *需求说明: 沪深两市板块分时行情数据查询,以交易时间和交易总金额降序查询,取前10条数据
     * @return
     */
    R<List<StockBlockRtInfo>> sectorAllLimit();

方法实现:

  
    //注入mapper接口
    @Autowired
    private StockBlockRtInfoMapper stockBlockRtInfoMapper;    
    /**
     *需求说明: 沪深两市板块分时行情数据查询,以交易时间和交易总金额降序查询,取前10条数据
     * @return
     */
    @Override
    public R<List<StockBlockRtInfo>> sectorAllLimit() {
        //1.调用mapper接口获取数据 TODO 优化 避免全表查询 根据时间范围查询,提高查询效率
         List<StockBlockRtInfo> infos=stockBlockRtInfoMapper.sectorAllLimit();
        //2.组装数据
        if (CollectionUtils.isEmpty(infos)) {
            return R.error(ResponseCode.NO_RESPONSE_DATA.getMessage());
        }
        return R.ok(infos);
    }
3)定义mapper方法与xml

mapper接口方法:

    /**
     * 沪深两市板块分时行情数据查询,以交易时间和交易总金额降序查询,取前10条数据
     * @return
     */
    List<StockBlockRtInfo> sectorAllLimit();

定义mapper接口xml:

    <select id="sectorAllLimit" resultType="com.itheima.stock.common.domain.StockBlockDomain">
        select
            sbr.company_num  as companyNum,
            sbr.trade_amount as tradeAmt,
            sbr.label        as code,
            sbr.avg_price    as avgPrice,
            sbr.block_name   as name,
            date_format(sbr.cur_time,'%Y%m%d') as curDate,
            sbr.trade_volume as tradeVol,
            sbr.updown_rate  as updownRate
        from stock_block_rt_info as sbr
        order by sbr.cur_time desc,sbr.trade_volume desc
            limit 10
    </select>
4) web接口测试

postman:http://127.0.0.1:8080/api/quot/sector/all

前端页面效果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值