这是我遇到的写完登录以及验证码功能后,进入首页时而显示验证码时而不显示,报错维护神领物流项目学习


分析问题:为什么验证码会一会出来一会没出来?

看前端请求,9527是网关,然后通过manager路由到微服务实例sl-ms-web-manger

神领物流项目学习

看一眼Nacos(负载均衡)中对应几个实例

神领物流项目学习

很明显是两个微服务实例,那我们到底用的哪个呢?要知道,这是在两个服务器上,代码不一致自然也就会出现负载均衡问题

神领物流项目学习

测试:

docker中停掉部署到jekins中的微服务实例

神领物流项目学习



神领物流项目学习

问题解决

神领物流项目学习

最后,在没完善代码之前不用提前部署微服务实例到jkins,如果部署了停掉jkins上代码不一致的,另一个服务器的微服务才能正常使用

或者每次写完代码提交重新部署一下(比较麻烦)




2025/6/30

业务问题?

使用token登录,token被盗

解决方案?

双Token三验证方案

具体业务实现?

神领物流项目学习

第一步:先做双Token 和access_token的验证(之前做过,在这基础上多了refresh_token)

小点是通过refresh_token存入redis中时用MD5加密一下

神领物流项目学习


神领物流项目学习


神领物流项目学习

第二步:access_token过期就会执行refresh方法

refresh方法的目的就是重新生成一遍access_token和refresh_token

神领物流项目学习


//面试中如果问 双token三验证?
/**
双Token:  access_token(短5min) 和 refresh_token(长24H)
三验证:   access_token   refresh_token    redis中的refresh_token
**/


第三步删除旧的token

神领物流项目学习

思考token被盗的情况,如何解决?

把24小时过期的token存到redis中,无论谁携带token来访问,
不仅要校验token是否失效还要校验token是否在我的redis中存着,如何发现token被盗直接删除redis的token让它马上失效

神领物流项目学习



双token三验证总结:

神领物流项目学习

面试题!!!
说下你们双 token 三验证的设计逻辑?
1.登录发 token:
用户登录时,系统生成短期 access_token(日常接口用)和长期 refresh_token(续期用),把 refresh_token 存 Redis,双 token 给前端。
2.业务访问校验:
前端带 access_token 调接口,业务系统先一次校验(检查 access_token 合法性),通过则正常返回,不通过返回 401 触发续期。
3.token 失效续期:
前端拿 refresh_token 调刷新接口,系统先二次校验(检查 refresh_token 合法性),通过后再三次校验(查 Redis 里的 refresh_token 是否已用,防止重复续期 )。三次都过,就生成新双 token,更新 Redis 并返回前端;校验失败就要求重新登录。
这样设计既保障了接口安全(多层校验防攻击),又能让用户无感续期(不用频繁登录),Redis 存 refresh_token 还能解决续期凭证的复用风险~


2025/7/1

运费模板

神领物流项目学习


后面这部分有时间再补上


2025/7/6

路线管理

1、需求分析:

神领物流项目学习

2、具体实现

准备机构坐标

神领物流项目学习

实现思路

神领物流项目学习

根据上图总体实现分成以下六步:

起点和终点机构id是否一样
路线类型是否符合规定
判断起点和终点的路线是否存在
判断起点和终点的机构是否为空
封装数据获取两个节点之间的距离开车时间总成本
新增操作

两个Neo4j语句比较重要,贴这了

神领物流项目学习

具体实现的业务逻辑如下:

  /**
     * 新增路线
     *
     * @param transportLine 路线数据
     * @return 是否成功
     */
    @Override
    public Boolean createLine(TransportLine transportLine) {
        Long startOrganId = transportLine.getStartOrganId();
        Long endOrganId = transportLine.getEndOrganId();
        //1、起点和终点机构id是否一样
        if (startOrganId.equals(endOrganId)) {
            throw new SLException(ExceptionEnum.TRANSPORT_LINE_ORGAN_CANNOT_SAME);
        }
        //2、路线类型是否符合规定   干线 支线 接驳路线
        //根据type获取枚举类
        TransportLineEnum transportLineEnum = TransportLineEnum.codeOf(transportLine.getType());
        BaseEntity firstNode = null;
        BaseEntity secondNode = null;
        //3、判断起点和终点的路线是否存在
        switch (transportLineEnum) {
            case TRUNK_LINE:
                firstNode = OLTEntity.builder().bid(startOrganId).build();
                secondNode = OLTEntity.builder().bid(endOrganId).build();
                break;
            case BRANCH_LINE:
                firstNode = OLTEntity.builder().bid(startOrganId).build();
                secondNode = TLTEntity.builder().bid(endOrganId).build();
                break;
            case CONNECT_LINE:
                firstNode = OLTEntity.builder().bid(startOrganId).build();
                secondNode = AgencyEntity.builder().bid(endOrganId).build();
                break;
            default:
                throw new SLException(ExceptionEnum.TRANSPORT_LINE_TYPE_ERROR);
        }
        //4、判断起点和终点的机构是否为空
        Long count = transportLineRepository.queryCount(firstNode, secondNode);
        if (count > 0) {
            throw new SLException(ExceptionEnum.TRANSPORT_LINE_ALREADY_EXISTS);
        }
        //5、封装数据获取两个节点之间的距离开车时间总成本
        initDataFormMap(firstNode, secondNode, transportLine);
        //6、新增操作
        Long lineId = transportLineRepository.create(firstNode, secondNode, transportLine);
        return lineId > 0;
    }

    //抽取方法:封装数据获取两个节点之间的距离开车时间总成本
    private void initDataFormMap(BaseEntity firstNode, BaseEntity secondNode, TransportLine transportLine) {
        //查询两个节点
        OrganDTO startOrgan = organReepository.findByBid(firstNode.getBid());
        OrganDTO endOrgan = organReepository.findByBid(secondNode.getBid());
        if (startOrgan == null || endOrgan == null) {
            throw new SLException(ExceptionEnum.ORGAN_NOT_FOUND);
        }
        //根据坐标调用高德地图
        if (startOrgan.getLatitude() != null && startOrgan.getLatitude() != null
                && endOrgan.getLongitude() != null && endOrgan.getLongitude() != null
        ) {
            Map<String, Object> param = new HashMap<>();
            param.put("show_fields", "cost");
            String driving = eagleMapTemplate.opsForDirection().driving(ProviderEnum.AMAP,
                    new Coordinate(startOrgan.getLongitude(), startOrgan.getLatitude()),
                    new Coordinate(endOrgan.getLongitude(), endOrgan.getLatitude()), param);
            System.out.println(driving);
            JSONObject jsonObject = JSONUtil.parseObj(driving);
            //获取总时间和距离
            Long time = jsonObject.getByPath("route.paths[0].cost.duration", Long.class);
            Long distance = jsonObject.getByPath("route.paths[0].distance", Long.class);
            transportLine.setTime(time);
            transportLine.setDistance(distance.doubleValue());

            //暂时给一个假的每公里的成本 0.5  TODO: 获取成本
            transportLine.setCost(distance/1000 * 0.5); //0.5元/公里
        }
    }

具体两个对应Neo4j语句的方法:

 /**
     * 查询数据节点之间的关系数量
     *
     * @param firstNode  第一个节点
     * @param secondNode 第二个节点
     * @return 数量
     */
    @Override
    public Long queryCount(BaseEntity firstNode, BaseEntity secondNode) {
        //MATCH (m:OLT) -[r]- (n:OLT) WHERE m.bid =10001 AND n.bid = 1002 RETURN count(r) AS c
        String firstType = firstNode.getClass().getAnnotation(Node.class).value()[0];
        String secondType = secondNode.getClass().getAnnotation(Node.class).value()[0];

        String cypher = StrUtil.format("MATCH (m:{}) -[r]- (n:{}) WHERE m.bid =$startId AND n.bid = $endId RETURN count(r) AS c",
                firstType, secondType);

        //执行查询
        return neo4jClient.query(cypher)
                .bind(firstNode.getBid()).to("startId")
                .bind(secondNode.getBid()).to("endId")
                .fetchAs(Long.class)
                .mappedBy((typeSystem, record) ->
                        record.get("c").asLong()
                ).one().orElse(0L);
    }

    /**
     * 新增路线
     *
     * @param firstNode     第一个节点
     * @param secondNode    第二个节点
     * @param transportLine 路线数据
     * @return 新增关系的数量
     */
    @Override
    public Long create(BaseEntity firstNode, BaseEntity secondNode, TransportLine transportLine) {
        //MATCH (m:OLT {bid : 10001})
        //WITH m  MATCH (n:OLT {bid : 1002})  WITH m,n
        //CREATE
        //(m) -[r:IN_LINE {cost:999.0, number:'LX001', type:1, name:'京广线', distance:4000, time:60, extra:'', startOrganId:1001, endOrganId:1002}]-> (n),
        //(m) <-[:OUT_LINE {cost:999.0, number:'LX002', type:1, name:'京广线', distance:4000, time:60, extra:'', startOrganId:1002, endOrganId:1001}]- (n)
        //RETURN count(r) AS c
        String firstType = firstNode.getClass().getAnnotation(Node.class).value()[0];
        String secondType = secondNode.getClass().getAnnotation(Node.class).value()[0];

        String cypher = StrUtil.format("  MATCH (m:{} {bid : $startId})n" +
                        "        WITH m  MATCH (n:{} {bid : $endId})  WITH m,nn" +
                        "        CREATEn" +
                        "                (m) -[r:IN_LINE {cost:$cost, number:'$number', type:$type, name:'$name', distance:$distance, time:$time, extra:'', startOrganId:$startOrganId, endOrganId:$endOrganId}]-> (n),n" +
                        "                (m) <-[:OUT_LINE {cost:$cost, number:'$number', type:$type, name:'$name', distance:$distance, time:$time, extra:'', startOrganId:$endOrganId, endOrganId:$startOrganId}]- (n)n" +
                        "                RETURN count(r) AS c",
                firstType, secondType);

        return neo4jClient.query(cypher)
                .bind(firstNode.getBid()).to("startId")
                .bind(secondNode.getBid()).to("endId")
                .bindAll(BeanUtil.beanToMap(transportLine))
                .fetchAs(Long.class)
                .mappedBy((typeSystem, record) ->
                        record.get("c").asLong()
                ).one().orElse(0L);
    }

ok以后就记住每天业务实现的思路即可


2025/7/7

1、面试问路线规划是怎么做的?(机构管理+线路管理)

路线规划也就是机构与机构之间的路线,一开始机构是在权限管家存储,机构数据通过MQ会同步存储到Neo4j(Neo4j就是图数据库方便存储机构与机构之间的路线关系),然后在后台添加机构与机构之间的路线(路线分成干线、支线、接驳路线),通过转运次数最少和成本最低两个方法选择机构之间的最佳路线。