一、需求描述

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页面的代码如下:

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导入所需依赖

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.7.3

top.roud

kd-query100

0.0.1-SNAPSHOT

kd-query100

kd-query100

1.8

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-devtools

runtime

true

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

com.alibaba

fastjson

1.2.76

org.apache.httpcomponents

httpclient

4.5.6

org.apache.httpcomponents

httpmime

4.5.6

org.apache.httpcomponents

httpcore

4.4.10

org.apache.commons

commons-lang3

3.8.1

org.springframework.boot

spring-boot-maven-plugin

org.projectlombok

lombok

spring-milestones

Spring Milestones

https://repo.spring.io/milestone

false

spring-snapshots

Spring Snapshots

https://repo.spring.io/snapshot

false

spring-milestones

Spring Milestones

https://repo.spring.io/milestone

false

spring-snapshots

Spring Snapshots

https://repo.spring.io/snapshot

false

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 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 list = new ArrayList();

Iterator var11 = params.entrySet().iterator();

while(var11.hasNext()) {

Map.Entry 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 objectToMap(Object obj) throws IllegalAccessException {

if (obj == null) {

return null;

} else {

Map map = new HashMap();

List 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 getAllField(Object obj) {

List 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 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 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 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仓库地址

精彩内容

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。