一、需求描述

1、需求

对接快递100快递查询接口,后端使用Springboot,前端使用vue2+element-plus,搭建一个简洁、美观、适配手机端PC端且前后端分离的快递查询网站项目。

2、工具

idea

3、项目准备

前往快递100API开放平台注册账号,注册用户有50个测试单量

4、效果

4.1 PC端效果展示
在这里插入图片描述
4.2 移动端效果展示
在这里插入图片描述

二、构建前端项目

1、安装vue脚手架
2、通过vue create创建项目

2.1 在项目构建目录下进入cmd黑窗口

2.2 输入命令创建vue项目

vue create 你的项目名

操作键指引:

按空格键选中
a子母键全选
i子母键反选
回车键确定到下一步

2.3 选择Manually select features,自己选择需要的功能
在这里插入图片描述

2.4 选择需要的配置项

  • Babel 支持babel(选上)
  • TypeScript 安装ts(选上)
  • Progressive Web App (PWA) Support(一般不选)
  • Router 路由模块(选上)
  • Vuex 状态管理(需要用到就选上)
  • CSS Pre-processors css预处理器(选上)
  • Linter / Formatter 代码校验(选上)
  • Unit Testing 单元测试(一般不需要)
  • E2E Testing 端到端测试(一般不用)

2.5 选择vue的版本
该项目选择2.x

2.6 选择配置中选择了TypeScript,就会有这一步,class-style component syntax 是否使用class类风格编码,选y

在这里插入图片描述
2.7 是否使用history路由模式,默认是hash模式,hash模式会在url后面带#f符号,选y
在这里插入图片描述
2.8 选择css预处理器类型,选Saas/SCSS
在这里插入图片描述
2.9 选择一个代码校验配置支持代码风格检查和格式化,选ESLint with error prevention only

  • ESLint with error prevention only (仅具有错误预防功能)
  • ESLint + Airbnb config (Airbnb配置)
  • ESLint + Standard config (标准配置)
  • ESLint + Prettier (Prettier)

2.10 选择什么时候校验格式,选Lint on save

  • Lint on save(保存时)
  • Lint and fix on commit(提交时)

2.11 选择配置文件的位置,由于每个插件都有自己单独的配置文件,所以择第一个
在这里插入图片描述

2.12 否将当前配置选项保存起来,方便下次创建项目时使用,选n
在这里插入图片描述
2.13 vue项目创建成功

三、创建Springboot项目

1、创建一个初始化项目

在这里插入图片描述

2、创建完Spring项目后将vue项目移动到该项目下

项目结构如下图,其中kd-query-vue是vue项目
在这里插入图片描述

3、配置启动类

3.1 配置Springboot项目启动类
在这里插入图片描述
在这里插入图片描述
3.2 配置vue项目启动类
在这里插入图片描述

四、构建前端项目

1、项目结构

在这里插入图片描述

2、main.js导入项目需要的包
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//mdeditor
import VueMarkdownEditor from '@kangc/v-md-editor';
import '@kangc/v-md-editor/lib/style/base-editor.css';
import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js';
import '@kangc/v-md-editor/lib/theme/style/vuepress.css';



//中文包
import zhCn from 'element-plus/es/locale/lang/zh-cn'

import Prism from 'prismjs';
//mdeditor
VueMarkdownEditor.use(vuepressTheme, {
  Prism,
});

const app = createApp(App)
// 图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}
app.use(store).use(VueMarkdownEditor).use(router).use(ElementPlus, {locale: zhCn,}).mount('#app')

3、新建utils/request.js用于发送axios请求
import axios from 'axios'

const request = axios.create({
    baseURL: '/api',
    timeout: 5000
})

request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    if (localStorage.getItem('token')) {
        config.headers['token'] = localStorage.getItem('token');
    }
    return config
}, error => {
    return Promise.reject(error)
});

request.interceptors.response.use(
    response => {
        let res = response.data;

        if (response.config.responseType === 'blob') {
            return res
        }
        if (typeof res === 'string') {
            res = res ? JSON.parse(res) : res
        }
        return res;
    },
    error => {
        console.log('err' + error);// for debug
        return Promise.reject(error)
    }
)


export default request

4、vue.config.js配置跨域请求

请求url与后端项目的请求地址对应

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true
});
// 跨域配置
module.exports = {
  devServer: {
    open: true,
    host: 'localhost',
    port: 8081,
    proxy: {
      '/api': {
        target: 'http://localhost:8923',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
};

5、新建views/Query.vue作为查询主页面

在这里插入图片描述

5.1、此处的页面设计

1、输入框,用于输入单号
2、按键,点击进行查询
3、选择框,用于选择快递公司
4、输入框,用于输入收件人或寄件人姓名(顺丰查询快递需要)
5、物流轨迹展示盒子,超出自动滚动
6、存放所有物流轨迹的盒子,置于物流轨迹展示盒子中

5.2 其他

1、标题为快递侠
2、版权展示,居中靠近底部

5.3 注意

1、为了兼容移动端和PC端,页面中的样式长宽高大部分使用百分比
2、对输入框进行简单校验
3、单号输入框进行回车操作后即可进行查询
4、对消息提示进行封装

物流轨迹选择的是element-plus中的 Timeline-时间线,通过自定义节点样式达到我们物流轨迹的效果,其中我们需要自定义的主要样式有内容、时间、节点颜色、节点图标,我们可以在后端通过物流轨迹内容给物流节点的颜色和图标赋值。

在这里插入图片描述

5.4 页面代码

请求后端返回的查询结果格式:

{
    "code": "200",
    "msg": "成功",
    "data": [
        {
            "content": "[河北省 石家庄市 裕华区]包裹已签收!(凭取件码)如有问题请电联:,投诉电话:0311-68048041",
            "timestamp": "2023-01-15 11:47:56",
            "icon": "Select",
            "color": "green",
            "code": 102
        },
        {
            "content": "[河北省 石家庄市 裕华区]您的包裹已存放至【代收点】,记得早点来【恒大雅苑百世快递恒大雅苑百世快递】取它回家!如有问题请联系:,投诉电话:0311-68048041",
            "timestamp": "2023-01-15 10:49:00",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[河北省 石家庄市 裕华区]【石家庄化校网点】的兔兔快递员刘辉(13888888888)正在派件(可放心接听952300专属热线),投诉电话:0311-68048041。今天的兔兔,体温正常,口罩戴好,消毒到位,即将为您派件。",
            "timestamp": "2023-01-15 08:28:57",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[河北省 石家庄市 裕华区]快件到达【石家庄化校网点】",
            "timestamp": "2023-01-15 08:20:57",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[河北省 石家庄市 裕华区]快件离开【石家庄转运中心】已发往【石家庄化校网点】",
            "timestamp": "2023-01-14 15:46:07",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[河北省 石家庄市 裕华区]快件到达【石家庄转运中心】",
            "timestamp": "2023-01-14 14:18:18",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[福建省 泉州市 晋江市]快件离开【泉州转运中心】已发往【石家庄转运中心】",
            "timestamp": "2023-01-13 03:12:30",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[福建省 泉州市 晋江市]快件到达【泉州转运中心】",
            "timestamp": "2023-01-13 03:08:07",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[福建省 三明市 沙县]快件离开【三明转运中心】已发往【泉州转运中心】",
            "timestamp": "2023-01-12 21:57:17",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[福建省 三明市 沙县]快件到达【三明转运中心】",
            "timestamp": "2023-01-12 21:54:58",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[福建省 三明市 沙县]快件离开【三明市沙县一部集散点】已发往【三明转运中心】",
            "timestamp": "2023-01-12 21:54:48",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[福建省 南平市 建阳区]快件离开【南平建阳区网点】已发往【三明市沙县一部集散点】",
            "timestamp": "2023-01-12 13:19:45",
            "icon": null,
            "color": null,
            "code": 101
        },
        {
            "content": "[福建省 南平市 建阳区]【南平建阳区网点】的练文斌(13888888888)已取件。投诉电话:0599-8500490",
            "timestamp": "2023-01-12 13:13:45",
            "icon": null,
            "color": null,
            "code": 101
        }
    ]
}

Query.vue页面的代码如下:

<template>
  <div class="common-layout">
    <el-container>
      <el-header style="padding: 0; margin: 0">
        <div style="height: 60px; background: #6c8fce; padding-left: 15%">
          <h3 style="height:60px; line-height:60px; color: white; font-size: 26px; font-weight: 500">快递侠</h3>
        </div>
      </el-header>
      <el-main style="height: calc(100vh - 120px);">
        <div class="user_input">
          <div style="position:relative; width: 70%;margin-left: 15%; margin-bottom: 30px;margin-top: 30px">
            <el-input
                v-model="kuaidinum"
                class="w-50 m-2 selfinput"
                size="mini"
                placeholder="输入单号"
                @keyup.enter = "query"
            />
            <el-button type="primary" class="self_btn" :icon="Search" @click="query">查询</el-button>
          </div>
          <div style="width: 70%;margin-left: 15%; margin-bottom: 30px">
            <el-select v-model="kuaidicom" class="m-2" placeholder="选择快递公司">
              <el-option
                  v-for="item in options"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
              />
            </el-select>
          </div>
          <div style="width: 70%;margin-left: 15%; margin-bottom: 30px;margin-top: 30px">
            <el-input v-model="phone" placeholder="收寄件人电话号码(顺丰必填)" />
          </div>
        </div>
        <div class="trackBox" style="width: 70%; margin-left: 15%; height: 400px; overflow: auto">
          <el-timeline>
            <el-timeline-item
                v-for="(item,index) in trackData"
                :key="index"
                :icon="item.icon"
                :color="item.color"
                :timestamp="item.timestamp"
            >
              {{ item.content }}
            </el-timeline-item>
          </el-timeline>
        </div>
      </el-main>
      <el-footer style="height: 60px">
        <p style="height: 60px; line-height:60px; text-align: center; font-weight: 300; font-size: 12px">©2023 http://www.roud.top</p>
      </el-footer>
    </el-container>
  </div>
</template>

<script>
import request from "../utils/request";
import {ElMessage} from "element-plus";

export default {
  name: "Query",
  data(){
    return{
      //快递公司选择框数据,value为发送表单时的值,键为对应的v-model,value值参考快递100API快递公司编码表(https://api.kuaidi100.com/manager/openapi/download/kdbm.do)
      options : [
        {
          value: 'zhongtong',
          label: '中通快递',
        },
        {
          value: 'jtexpress',
          label: '极兔速递',
        },
        {
          value: 'yunatong',
          label: '圆通速递',
        },
        {
          value: 'shunfeng',
          label: '顺丰速运',
        },
        {
          value: 'yunda',
          label: '韵达快递',
        },
        {
          value: 'shentong',
          label: '申通快递',
        },
        {
          value: 'youzhengguonei',
          label: '邮政速递',
        },{
          value: 'ems',
          label: 'EMS',
        },
        {
          value: 'jd',
          label: '京东物流',
        },
        {
          value: 'debangkuaidi',
          label: '德邦快递',
        },
        {
          value: 'debang物流',
          label: '德邦物流',
        },
        {
          value: 'fengwang',
          label: '丰网速运',
        },
        {
          value: 'sxjdfreight',
          label: '顺心捷达',
        },
        {
          value: 'zhaijisong',
          label: '宅急送',
        },
        {
          value: 'danniao',
          label: '丹鸟',
        },
        {
          value: 'shunfengkuaiyun',
          label: '顺丰快运',
        },
        {
          value: 'shunfengkuaiyun',
          label: '顺丰快运',
        },
        {
          value: 'disifang',
          label: '递四方',
        },{
          value: 'zhuanyunsifang',
          label: '转运四方',
        },{
          value: 'ups',
          label: 'UPS',
        },{
          value: 'jinguangsudikuaijian',
          label: '京广速递',
        },
        {
          value: 'yimidida',
          label: '壹米滴答',
        },
        {
          value: 'shunfengkuaiyun',
          label: '顺丰快运',
        }
      ],
      kuaidinum: "",
      kuaidicom: "",
      phone: "",
      trackData: [],
      form: {}
    }
  },
  methods:{
    //成功提示
    showSuccessMessage(msg){
      ElMessage.success({
        message: msg,
      });
    },
    //失败提示
    showFailMessage(msg){
      ElMessage.error({
        message: msg,
      });
    },
    //警告提示
    showWarningMessage(msg){
      ElMessage.warning({
        message: msg,
      });
    },
    //点击查询执行的方法
    query(){
      this.form = {}
      this.trackData = []
      //判断输入是否为空
      if(this.kuaidinum === "" || this.kuaidinum == null || this.kuaidicom == null || this.kuaidicom === ""){
        this.showWarningMessage("请输入准确的信息!");
        return;
      }
      //对单号进行简单检验
      var reg = /^[a-zA-Z]*[0-9]+$/
      if(!reg.test(this.kuaidinum)){
        this.showWarningMessage("请输入正确的单号!");
        return;
      }
      this.form.num = this.kuaidinum;
      this.form.com = this.kuaidicom;
      if("shunfeng" === this.kuaidicom){
        this.form.phone = this.phone;
      }
      //发送查询请求
      request.post("/query/kd100", this.form).then(res => {
        //处理查询结果
        let data = res["data"];
        if((data[0])["code"] === 101){
          (data[0])["color"] = "green";
        }
        if(data.length > 1 && (data[data.length-1])["code"] === 101){
          (data[data.length-1])["icon"] = "ArrowDownBold"
        }
        if((data[0])["code"] === 101 || (data[0])["code"] === 102){
          this.showSuccessMessage("查询成功");
        }else {
          this.showWarningMessage("查询异常")
        }
        this.trackData = data.reverse();
      });
    }
  }
}
</script>

<style>
  *{
    padding: 0;
    margin: 0;
  }
  .el-select{
    width: 100%;
  }
  .selfinput{
    width: 90% !important;
  }
  .self_btn{
    position: absolute;
    right: 0 !important;
  }

  ::-webkit-scrollbar {
    height: 6px;
    width: 3px;
  }
  ::-webkit-scrollbar-track {
    background-color: transparent;
  }
  ::-webkit-scrollbar-thumb {

    box-shadow: inset 0 0 0 rgba(240, 240, 240, .5);
    border-radius: 2px;
    background-color: rgba(240, 240, 240, .5);
  }
</style>
6、router/index.js配置路由

配置默认路由及/kdx/query为该查询页面

import { createRouter, createWebHistory } from 'vue-router'
import Query from "@/views/Query";

const routes = [
  {
    path: '/kdx/query',
    name: 'query',
    component: Query,
  },
  {
    path: '/',
    name: 'q',
    component: Query,
  }
    ]
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

五、构建后端项目

1、项目结构

在这里插入图片描述

2、pom.xml导入所需依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.roud</groupId>
    <artifactId>kd-query100</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>kd-query100</name>
    <description>kd-query100</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.10</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>

</project>

3、src/main/resources/application.yml配置文件

这里简单配置一下项目端口

server:
  port: 8923
4、新建src/main/java/top/roud/kdquery100/utils包存放工具类
4.1 http包

用于存放http请求工具类

目录结构如下:
在这里插入图片描述

4.1.1 HttpResult请求结果封装类

package top.roud.kdquery100.utils.http;

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

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/4
 * @version:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HttpResult {
    private int status;
    private String body;
    private String error;
}

4.1.2 HttpUtil请求工具类

package top.roud.kdquery100.utils.http;

import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/4
 * @version:
 */
public class HttpUtil {
    public static HttpResult doPost(String url, Object obj, int connectTimeout, int socketTimeout) {
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        CloseableHttpResponse resp = null;
        HttpResult result = new HttpResult();

        try {
            Map<String, String> params = ObjectToMapUtil.objectToMap(obj);
            HttpPost httpPost = new HttpPost(url);
            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build();
            httpPost.setConfig(requestConfig);
            if (params != null && params.size() > 0) {
                List<NameValuePair> list = new ArrayList();
                Iterator var11 = params.entrySet().iterator();

                while(var11.hasNext()) {
                    Map.Entry<String, String> entry = (Map.Entry)var11.next();
                    list.add(new BasicNameValuePair((String)entry.getKey(), (String)entry.getValue()));
                }
//                httpPost.setHeader("Content-Type","application/x-www-form-urlencoded");
                httpPost.setEntity(new UrlEncodedFormEntity(list, "UTF-8"));
            }

            resp = httpClient.execute(httpPost);
            String body = EntityUtils.toString(resp.getEntity(), "UTF-8");
            int statusCode = resp.getStatusLine().getStatusCode();
            result.setStatus(statusCode);
            result.setBody(body);
        } catch (Exception var21) {
            var21.printStackTrace();
        } finally {
            if (null != resp) {
                try {
                    resp.close();
                } catch (IOException var20) {
                    var20.printStackTrace();
                }
            }

        }
        return result;
    }
}

4.1.3 ObjectToMapUtil

package top.roud.kdquery100.utils.http;

import java.lang.reflect.Field;
import java.util.*;

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/4
 * @version:
 */
public class ObjectToMapUtil {
    public ObjectToMapUtil() {
    }

    public static Map<String, String> objectToMap(Object obj) throws IllegalAccessException {
        if (obj == null) {
            return null;
        } else {
            Map<String, String> map = new HashMap();
            List<Field> allField = getAllField(obj);

            String fieldName;
            String fieldValue;
            for(Iterator var3 = allField.iterator(); var3.hasNext(); map.put(fieldName, fieldValue)) {
                Field field = (Field)var3.next();
                field.setAccessible(true);
                fieldName = field.getName();
                fieldValue = "";
                if (field.getType() == String.class || field.getType() == Integer.class || field.getType() == Integer.TYPE) {
                    fieldValue = field.get(obj) == null ? "" : field.get(obj).toString();
                }
            }

            return map;
        }
    }

    private static List<Field> getAllField(Object obj) {
        List<Field> fieldList = new ArrayList();

        for(Class clazz = obj.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
        }

        return fieldList;
    }
}
4.2 md包

主要存放md5加密的工具类
目录结构:
在这里插入图片描述
DigestUtil 加密工具类

package top.roud.kdquery100.utils.md;

import org.springframework.util.DigestUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/18
 * @version:
 */
public class DigestUtil {
    public static String md5(String str) {
        String md5 = "";
        try {
            md5 = DigestUtils.md5DigestAsHex(str.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return md5;
    }
}
4.3 rusult包

主要存放返回给用户的结果的封装类
目录结构:
在这里插入图片描述
4.3.1 StatusCode 返回结果状态码枚举类

package top.roud.kdquery100.utils.result;

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/6
 * @version:
 */
public enum StatusCode {
    SUCCESS("200","成功"),
    ERROR("500","服务器错误"),
    FAIL("501","查无结果"),
    ACCOUNT_CHECK_FAIL("301","账号校验失败");

    private String code;

    public String getCode() {
        return code;
    }

    private String message;

    public String getMessage() {
        return message;
    }

    StatusCode(String code, String message) {
        this.code = code;
        this.message = message;
    }

}

4.3.2 StatusCode返回结果类

package top.roud.kdquery100.utils.result;

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

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/6
 * @version:
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private String code;
    private String msg;
    private Object data;
    public Result(StatusCode sc, Object data){
        this.code = sc.getCode();
        this.msg = sc.getMessage();
        this.data = data;
    }
}

5、新建src/main/java/top/roud/kdquery100/entity包存放实体类

目录结构如下:
在这里插入图片描述
根据快递100实时查询API技术文档的参数要求构建请求实体类,包括请求、请求参数、请求结果

在这里插入图片描述
5.1 QueryParam 查询参数类

package top.roud.kdquery100.entity;

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

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/18
 * @version:
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class QueryParam {
        private String com;
        private String num;
        private String phone;
        private String from;
        private String to;
        private String resultv2;
        private String show;
        private String order;
}

5.2 QueryRequest 查询请求类

package top.roud.kdquery100.entity;

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

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/18
 * @version:
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class QueryRequest {
    private String customer;
    private String sign;
    private String param;
}

5.3 QueryResult 查询结果类

package top.roud.kdquery100.entity;

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

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/19
 * @version:
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class QueryResult {
    private String content;
    private String timestamp;
    private String icon;
    private String color;
    /**
     * 101  在途中
     * 102  已签收
     * 103  查无结果
     * 104  服务器错误
     */
    private Integer code;

}
6、业务类编写

目录结构:
在这里插入图片描述
6.1 QueryServiceImpl 查询业务接口

package top.roud.kdquery100.service.impl;

import top.roud.kdquery100.entity.QueryParam;
import top.roud.kdquery100.utils.result.Result;

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/18
 * @version:
 */
public interface QueryServiceImpl {
    public Result queryKdTrack(QueryParam param);
}

6.2 QueryService 查询业务实现类

需要前往快递100API开放平台注册账号,注册用户有50个测试单量。在我的信息-企业信息中获取授权Key和customer替换填入代码中
在这里插入图片描述
代码如下,根据完成的前端项目所需的JSON格式进行构造:

package top.roud.kdquery100.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import top.roud.kdquery100.entity.QueryParam;
import top.roud.kdquery100.entity.QueryRequest;
import top.roud.kdquery100.entity.QueryResult;
import top.roud.kdquery100.service.impl.QueryServiceImpl;
import top.roud.kdquery100.utils.http.HttpResult;
import top.roud.kdquery100.utils.http.HttpUtil;
import top.roud.kdquery100.utils.md.DigestUtil;
import top.roud.kdquery100.utils.result.Result;
import top.roud.kdquery100.utils.result.StatusCode;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/18
 * @version:
 */
@Service
public class QueryService implements QueryServiceImpl{

    @Override
    public Result queryKdTrack(QueryParam param) {
        String paramStr = JSONObject.toJSONString(param);
        QueryRequest queryRequest = new QueryRequest();
        queryRequest.setParam(paramStr);
        queryRequest.setCustomer("快递100API企业管理后台customer");
        String sign = DigestUtil.md5(paramStr + "快递100API企业管理后台key" + "快递100API企业管理后台customer").toUpperCase();
        queryRequest.setSign(sign);
        ArrayList<QueryResult> list = new ArrayList<>();
        try{
            HttpResult httpResult = HttpUtil.doPost("https://poll.kuaidi100.com/poll/query.do", queryRequest, 3000, 300);
            if(httpResult.getStatus() == 200){
                String body = httpResult.getBody();
                JSONObject jsonObject = JSONObject.parseObject(body);
                if(StringUtils.contains(body, "\"status\":\"200\"")){
                    JSONArray datas = jsonObject.getJSONArray("data");
                    List<QueryResult> resList = datas.stream().map(o -> {
                        QueryResult queryResult = new QueryResult();
                        String ftime = ((JSONObject) JSONObject.toJSON(o)).getString("ftime");
                        String content = ((JSONObject) JSONObject.toJSON(o)).getString("context");
                        queryResult.setContent(content);
                        if(StringUtils.contains(content,"签收")){
                            queryResult.setIcon("Select");
                            queryResult.setColor("green");
                            queryResult.setCode(102);
                        }else {
                            queryResult.setCode(101);
                        }
                        queryResult.setTimestamp(ftime);
                        return queryResult;
                    }).collect(Collectors.toList());
                    return new Result(StatusCode.SUCCESS,resList);
                }else{
                    return getResultList(list,"查无结果", "orange", "SemiSelect", 103, StatusCode.FAIL);
                }
            }
        }catch (Exception e){
            return getResultList(list,"服务器异常,请联系管理员修复", "red", "CloseBold",104, StatusCode.ERROR);
        }
        return getResultList(list,"服务器异常,请联系管理员修复", "red", "CloseBold",104, StatusCode.ERROR);
    }

    private Result getResultList(ArrayList<QueryResult> list, String content, String color, String icon, Integer code, StatusCode sc) {
        String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        QueryResult queryResult = new QueryResult();
        queryResult.setTimestamp(time);
        queryResult.setContent(content);
        queryResult.setIcon(icon);
        queryResult.setColor(color);
        queryResult.setCode(code);
        list.add(queryResult);
        return new Result(sc, list);
    }
}
7、 控制层

目录结构:
在这里插入图片描述
QueryController 查询控制层类,定义的接口入口为/query/kd100

package top.roud.kdquery100.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.roud.kdquery100.entity.QueryParam;
import top.roud.kdquery100.service.impl.QueryServiceImpl;
import top.roud.kdquery100.utils.result.Result;

/**
 * @description : TODO
 * @author: roud
 * @date: 2023/1/18
 * @version:
 */
@RestController
@RequestMapping("query")
public class QueryController {
    @Autowired
    private QueryServiceImpl queryService;
    @PostMapping("/kd100")
    public Result query(@RequestBody QueryParam param){
        return queryService.queryKdTrack(param);
    }
}

六、前后端联调测试

先启动后端项目,再启动前端项目。
实现效果如下:
在这里插入图片描述
项目完成,撒花~~~

七、项目地址

点此进入该项目的github仓库地址

Logo

前往低代码交流专区

更多推荐