今日指数-day08
1. 个股最新分时行情数据
1.1 个股最新分时行情功能说明
1)个股最新分时行情功能原型
2)个股最新分时行情数据接口分析
功能描述:
获取个股最新分时行情数据,主要包含:
开盘价、前收盘价、最新价、最高价、最低价、成交金额和成交量、交易时间信息;
服务路径:/api/quot/stock/screen/second/detail
服务方法:GET
请求参数:code //股票编码
响应数据格式:
R<StockRt>
{
"code": 1,
"data": {
"tradeAmt": 58672751,//最新交易量
"preClosePrice": 3.89,//前收盘价格
"lowPrice": 3.89,//最低价
"highPrice": 3.91,//最高价
"openPrice": 3.9,//开盘价
"tradeVol": 228625157,//交易金额
"tradePrice": 3.9//当前价格
"curDate": '2022-01-03 14:58'//当前日期
}
}
2)注意事项
如果当前日期不在股票交易时间内,则查询最近的股票交易时间的数据回显
代码实现
1. 表现层
/**
* 个股最新分时行情数据
*/
@ApiOperation(value = "个股最新分时行情数据", notes = "个股最新分时行情数据", httpMethod = "GET")
@GetMapping("/stock/screen/second/detail")
public R<MarketDetialDomain> getMarketDetial(@RequestParam(name = "code" , required = true) String code){
return service.getMarketDetial(code);
}
2. 服务层
R<MarketDetialDomain> getMarketDetial(String code);
@Override
public R<MarketDetialDomain> getMarketDetial(String code) {
// 获取最新时间
DateTime curDate = DateTimeUtil.getLastDate4Stock(DateTime.now());
// 制造mock数据
curDate = DateTime.parse("2022-07-07 14:55:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
Date date = curDate.toDate();
// 查询数据
MarketDetialDomain msg = stockRtInfoMapper.getMarketDetial(date , code);
// 封装数据
return R.ok(msg);
}
3. Dao层
MarketDetialDomain getMarketDetial(@Param("date") Date date, @Param("code") String code);
<select id="getMarketDetial" resultType="com.jixu.stock.pojo.domain.MarketDetialDomain">
select trade_amount as tradeAmt,
pre_close_price as preClosePrice,
min_price as lowPrice,
max_price as highPrice,
open_price as openPrice,
trade_volume as tradeVol,
cur_price as tradePrice,
date_format(cur_time,'%Y%m%d%H%i') as curDate
from stock_rt_info where stock_code = #{code}
and cur_time = #{date}
</select>
2.个股实时交易流水查询
2.1 个股实时交易流水查询功能介绍
1)功能原型
2)功能接口说明
功能描述:个股交易流水行情数据查询--查询最新交易流水,按照交易时间降序取前10
服务路径:/quot/stock/screen/second
入参:code 股票编码
服务方法:GET
响应数据格式:
{
"code": 1,
"data": [
{
"date": "2022-01-03-14:58",//当前时间,精确到分
"tradeAmt": 58672751,//交易量
"tradeVol": 228625157,//交易金额
"tradePrice": 3.9//交易价格
}
]
}
代码实现
1. 表现层
/**
* 个股实时交易流水查询
*/
@ApiOperation(value = "个股实时交易流水查询", notes = "个股实时交易流水查询", httpMethod = "GET")
@GetMapping("/stock/screen/second")
public R<List<Map<String,Object>>> getMarketBill(@RequestParam(name = "code" , required = true) String code){
return service.getMarketBill(code);
2. 服务层
R<List<Map<String, Object>>> getMarketBill(@Param("code") String code);
@Override
public R<List<Map<String, Object>>> getMarketBill(String code) {
// 获取最新时间
DateTime curDate = DateTimeUtil.getLastDate4Stock(DateTime.now());
// 创建一个格式化器来将 DateTime 转换为字符串
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
String s = formatter.print(curDate);
curDate = DateTime.parse(s, DateTimeFormat.forPattern("yyyy-MM-dd"));
// 制造mock数据
curDate = DateTime.parse("2022-07-07", DateTimeFormat.forPattern("yyyy-MM-dd"));
Date date = curDate.toDate();
List<Map<String, Object>> msg = stockRtInfoMapper.getMarketBill(code,date);
return R.ok(msg);
}
3. Dao层
List<Map<String, Object>> getMarketBill(@Param("code") String code , @Param("date") Date date);
<select id="getMarketBill" resultType="java.util.Map">
select date_format(cur_time, '%Y%m%d%H%i') as date,
trade_amount as tradeAmt,
trade_volume as tradeVol,
cur_price as tradePrice
from stock_rt_info
where DATE(cur_time) = #{date}
and stock_code = #{code}
order by cur_time desc
limit 20;
</select>
3.拉取外盘数据功能实现
3.1功能分析
国外大盘数据采集与国内大盘数据几乎一致,目前通过sina接口无法获取国外大盘的交易量和交易金额数据,所以针对国外大盘数据,需要单独处理;
注意事项:
国外大盘数据接口不提供交易量和交易金额的信息;
字段分析:
var hq_str_b_FSSTI="富时新加坡海峡时报指数,3123.68,-2.96,-0.09";
大盘code 大盘名称 大盘点数 涨跌值 涨幅
注意:因为外盘的开盘周期不固定的,所以我们就一天仅仅采集一次数据即可;
或者针对不同的外盘,使用不同的采集计划!
采集外盘接口:http://hq.sinajs.cn/list=int_dji,int_nasdaq,int_hangseng,int_nikkei,b_FSSTI,其它详见第五天接口说明;
/**
* 外盘数据采集
*/
void getOutMarket();
/**
* 外盘数据采集
*/
@Override
public void getOutMarket() {
String url = stockInfoConfig.getOutMarket() + String.join("," , stockInfoConfig.getOuter());
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
int statusCodeValue = exchange.getStatusCodeValue();
if (statusCodeValue != 200) {
log.error("当前时间点{} , 数据采集失败 , 状态码{}", DateTime.now().toString("yyyy-MM-dd HH-mm-ss"), statusCodeValue);
return;
}
String jsData = exchange.getBody();
List<StockOuterMarketIndexInfo> list = parserStockInfoUtil.parser4StockOrMarketInfo(jsData, ParseType.OUTER);
log.info("采集的当前大盘数据:{}", list);
int num = stockOuterMarketIndexInfoMapper.insertOutMarketData(list);
if (num > 0) {
log.info("当前时间点{} , 数据插入成功", DateTime.now().toString("yyyy-MM-dd HH-mm-ss"));
rabbitTemplate.convertAndSend("stockExchange","inner.market",new Date());
} else {
log.error("当前时间点{} , 数据插入失败", DateTime.now().toString("yyyy-MM-dd HH-mm-ss"));
}
}
mapper层
int insertOutMarketData(@Param("list") List<StockOuterMarketIndexInfo> list);
<insert id="insertOutMarketData">
insert into stock_outer_market_index_info
( id,market_code,market_name
,cur_point,updown,rose
,cur_time)
values
<foreach collection="list" item="data" separator=",">
(#{data.id,jdbcType=BIGINT},#{data.marketCode,jdbcType=CHAR},#{data.marketName,jdbcType=VARCHAR}
,#{data.curPoint,jdbcType=DECIMAL},#{data.updown,jdbcType=DECIMAL},#{data.rose,jdbcType=DECIMAL}
,#{data.curTime,jdbcType=TIMESTAMP})
</foreach>
</insert>
4.完善用户登录成功动态回显菜单栏功能
1)功能接口说明
功能描述:当前用户登录后,仅仅加载了用户表相关信息,接下来完成的功能是完善用户权限相关的信息;
服务路径:/api/login
请求方式:POST
注意事项:顶级权限(功能菜单项)的pid为0
对应表表结构:
sys_permissioin表:
接口响应数据格式:
{
"code": 1,
"data": {
"id": "1237361915165020161",//用户ID
"username": "admin",//用户名称
"phone": "13888888888",//手机号
"nickName": "itheima",//昵称
"realName": "heima",//真实名称
"sex": 1,//性别
"status": 1,//状态
"email": "875267425@qq.com",//邮件
"menus": [//侧边栏权限树(不包含按钮权限)
{
"id": "1236916745927790564",//权限ID
"title": "组织管理",//权限标题
"icon": "el-icon-star-off",//权限图标(按钮权限无图片)
"path": "/org",//请求地址
"name": "org",//权限名称对应前端vue组件名称
"children": [
{
"id": "1236916745927790578",
"title": "角色管理",
"icon": "el-icon-s-promotion",
"path": "/roles",
"name": "roles",
"children": [] // null 则前端展示失败
},
{
"id": "1236916745927790560",
"title": "菜单权限管理",
"icon": "el-icon-s-tools",
"path": "/menus",
"name": "menus",
"children": []
}
]
},
{
"id": "1236916745927790569",
"title": "账号管理",
"icon": "el-icon-s-data",
"path": "/user",
"name": "user",
"children": []
}
],
permissions: ["btn-user-delete",//按钮权限标识
"btn-log-delete",
"btn-user-add",
"btn-role-update",
"btn-permission-delete",…]
}
}
实现步骤:
1.根据用户名已经查询了用户信息,且做了用户信息合法性的判断;
2.如果 用户合法,则根据用户的id去数据库查询用户拥有的权限信息集合;
根据用户id查询权限信息,数据要注意去重(distinct)
3.递归用户拥有的权限集合,组织出用户的目录-菜单树(不包含按钮权限)—》menus
3.获取按钮权限标识集合(获取权限集合中type=3的权限信息)—》permissions
提示:用户侧边栏信息先批量查询,然后再通过递归组装数据;
代码实现
1. 表现层
@ApiOperation(value = "登录功能")
// 该请求传入的参数为Json类型 , 我们需要将Json类型的数据反序列化为对象需要调用RequestBody
@PostMapping("/login")
public R<LoginRespVo> login(@RequestBody LoginReqVo loginReqVo){
return UserService.login(loginReqVo);
}
封装实体类
package com.jixu.stock.vo.resp;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author by itheima
* @Date 2021/12/24
* @Description 登录后响应前端的vo
*/
@ApiModel(description = "登录后响应前端的vo")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LoginRespVo {
/**
* 在前端中long类型数据过长会导致数据失真 英雌我们在传递的过程中需要将数据转化为String类型
*/
//用户ID
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
//用户名称
private String username;
//手机号
private String phone;
//昵称
private String nickName;
//真实名称
private String realName;
//性别
private Integer sex;
//状态
private Integer status;
//邮件
private String email;
//侧边栏权限树
private List menus;
//按钮权限标识
private List permissions;
}
package com.jixu.stock.pojo.domain;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @program: stock_parent
* @description:
* @author: jixu
* @create: 2024-10-05 17:57
**/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginMenusDomain {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String title;
private String icon ;
private String path;
private String name;
private List children;
}
2. 服务层
这个完善用户功能最大的难点是在于如何封装用户权限数据
- 从数据库中获取该用户的所有权限信息
- 将所有信息以Map的形式进行封装 用户id作为key
- 找到pid对应的id , 封装进另一个Map中 , 进行映射
public R<LoginRespVo> login(LoginReqVo loginReqVo);
/**
* 实现登录功能
* @param loginReqVo
* @return
*/
@Override
public R<LoginRespVo> login(LoginReqVo loginReqVo) {
// 1. 判断数据是否合法
if ( loginReqVo == null || StringUtils.isBlank(loginReqVo.getUsername()) || StringUtils.isBlank(loginReqVo.getPassword())){
return R.error(ResponseCode.DATA_ERROR.getMessage());
}
// 判断验证码以及sessionId是否存在
if ( StringUtils.isBlank(loginReqVo.getCode()) || StringUtils.isBlank(loginReqVo.getSessionId())){
return R.error(ResponseCode.CHECK_CODE_NOT_EMPTY.getMessage());
}
// 校验验证码是否正确
String redisCode = (String) redisTemplate.opsForValue().get(StockConstant.CHECK_PREFIX + loginReqVo.getSessionId());
if ( StringUtils.isBlank(redisCode) ){
return R.error(ResponseCode.CHECK_CODE_TIMEOUT.getMessage());
}
// 判断验证码是否正确
if ( !redisCode.equalsIgnoreCase(loginReqVo.getCode()) ){
return R.error(ResponseCode.CHECK_CODE_ERROR.getMessage());
}
// 2. 根据用户名查询用户数据
SysUser sysUser = sysUserMapper.selectByName(loginReqVo.getUsername());
if (sysUser == null){
return R.error(ResponseCode.ACCOUNT_NOT_EXISTS.getMessage());
}
// 3. 判断用户数据是否正确
if (!passwordEncoder.matches(loginReqVo.getPassword(),sysUser.getPassword())) {
return R.error(ResponseCode.USERNAME_OR_PASSWORD_ERROR.getMessage());
}
// 4. 装配数据 --> 使用BeanUtils中的工具包copyProperties可以直接将前一个对象的数据装配到后一个对象
// 前提是保证两个对象的属性名相同
LoginRespVo loginRespVo = new LoginRespVo();
BeanUtils.copyProperties(sysUser , loginRespVo);
// 获取用户ID
Long id = sysUser.getId();
// 查询获取主要权限
List<Map<String , Object>> menusData = sysPermissionMapper.getLoginMenus(id);
// // 获取所有id
// Set<Long> set = menusData.stream().map(item -> {
// Long roleid = (Long) item.get("id");
// return roleid;
// }).collect(Collectors.toSet());
ArrayList<UserLoginMenusDomain> userLoginMenus = new ArrayList<>();
// permissions
ArrayList<String> permissions = new ArrayList<>();
// 获取所有的数据集对象
HashMap<Long, UserLoginMenusDomain> menusDomainHashMap = new HashMap<Long, UserLoginMenusDomain>();
// 获取用户对应岗位
ArrayList<UserLoginMenusDomain> zeroData = new ArrayList<>();
for (Map<String, Object> item : menusData) {
Long pid = (Long) item.get("pid");
Long user_id = (Long) item.get("id");
String title = (String) item.get("title");
String path = (String) item.get("path");
String icon = (String) item.get("icon");
String name = (String) item.get("name");
String code = (String) item.get("code");
if (!StringUtils.isBlank(code)){
permissions.add(code);
}
UserLoginMenusDomain userLoginMenusDomain = new UserLoginMenusDomain();
userLoginMenusDomain.setId(user_id);
userLoginMenusDomain.setIcon(icon);
userLoginMenusDomain.setPath(path);
userLoginMenusDomain.setName(name);
userLoginMenusDomain.setTitle(title);
userLoginMenusDomain.setChildren(new ArrayList());
menusDomainHashMap.put(user_id , userLoginMenusDomain);
if (pid == 0){
zeroData.add(userLoginMenusDomain);
}
}
// 用于快速查找子菜单的映射
Map<Long, List<UserLoginMenusDomain>> childrenMap = new HashMap<>();
// 首先,构建一个映射,将每个父ID映射到其子菜单列表
for (Map<String, Object> menusDatum : menusData) {
Long pid = (Long) menusDatum.get("pid");
// 循环获取每个对象的id
Long id1 = (Long) menusDatum.get("id");
// 从数据集中查询获取该循环的对象
UserLoginMenusDomain domain = menusDomainHashMap.get(id1);
// 如果可以查询到
if (domain != null) {
// 创建一个对应的映射菜单 --> pid : List<该pid对应的父对象>
childrenMap.computeIfAbsent(pid, k -> new ArrayList<>()).add(domain);
}
}
// 假设 menusDomainHashMap 已经根据菜单项的 ID 填充了 UserLoginMenusDomain 对象
for (UserLoginMenusDomain zeroDatum : zeroData) {
Long parentId = zeroDatum.getId();
ArrayList<UserLoginMenusDomain> childrenList = new ArrayList<>();
// 现在,我们可以轻松地找到每个主菜单项的子菜单
if (childrenMap.containsKey(parentId)) {
List<UserLoginMenusDomain> directChildren = childrenMap.get(parentId);
for (UserLoginMenusDomain child : directChildren) {
// 递归地为每个子菜单设置其子菜单(如果有的话)
List<UserLoginMenusDomain> grandChildren = childrenMap.getOrDefault(child.getId(), new ArrayList<>());
child.setChildren(grandChildren);
}
childrenList.addAll(directChildren);
}
zeroDatum.setChildren(childrenList);
}
loginRespVo.setMenus(zeroData);
loginRespVo.setPermissions(permissions);
return R.ok(loginRespVo);
}
3. Dao层
List<Map<String, Object>> getLoginMenus(@Param("id") Long id);
<select id="getLoginMenus" resultType="java.util.Map">
select sys_permission.id as id,
sys_permission.title as title,
sys_permission.icon as icon,
sys_permission.url as path,
sys_permission.name as name,
sys_permission.code as code,
sys_permission.pid as pid
from sys_user_role
join sys_user on sys_user.id = sys_user_role.user_id
join sys_role on sys_user_role.role_id = sys_role.id
join sys_role_permission on sys_user_role.role_id = sys_role_permission.role_id
join sys_permission on sys_role_permission.permission_id = sys_permission.id
where sys_user.id = #{id}
</select>