新增/修改时间保存到毫秒
drop table if exists `passenger`; create table `passenger` ( `id` bigint not null comment 'id', `member_id` bigint not null comment '会员id', `name` varchar(20) not null comment '姓名', `id_card` varchar(18) not null comment '身份证', `type` char(1) not null comment '旅客类型|枚举[PassengerTypeEnum]', `create_time` datetime(3) comment '新增时间', `update_time` datetime(3) comment '修改时间', primary key (`id`), index `member_id_index` (`member_id`) ) engine=innodb default charset=utf8mb4 comment='乘车人';
用generator-config-member.xml生成持久层代码
<table tableName="passenger" domainObjectName="Passenger"/>
打开方式Maven--generator--Plugins--mybatis-generator--双击
增加乘车人类型枚举
1 package com.zihans.train.member.enums;
2
3 import java.util.ArrayList;
4 import java.util.EnumSet;
5 import java.util.HashMap;
6 import java.util.List;
7
8 public enum PassengerTypeEnum {
9
10 ADULT("1", "成人"),
11 CHILD("2", "儿童"),
12 STUDENT("3", "学生");
13
14 private String code;
15
16 private String desc;
17
18 PassengerTypeEnum(String code, String desc) {
19 this.code = code;
20 this.desc = desc;
21 }
22
23 public String getCode() {
24 return code;
25 }
26
27 public void setCode(String code) {
28 this.code = code;
29 }
30
31 public void setDesc(String desc) {
32 this.desc = desc;
33 }
34
35 public String getDesc() {
36 return desc;
37 }
38
39 public static List<HashMap<String,String>> getEnumList() {
40 List<HashMap<String, String>> list = new ArrayList<>();
41 for (PassengerTypeEnum anEnum : EnumSet.allOf(PassengerTypeEnum.class)) {
42 HashMap<String, String> map = new HashMap<>();
43 map.put("code",anEnum.code);
44 map.put("desc",anEnum.desc);
45 list.add(map);
46 }
47 return list;
48 }
49 }
PassengerTypeEnum.java
暂时与Passager相同,设置属性不为空。
1 package com.zihans.train.member.req;
2
3 import jakarta.validation.constraints.NotBlank;
4
5 import java.util.Date;
6
7 public class PassengerSaveReq {
8 private Long id;
9
10 //@NotNull(message = "【会员ID】不能为空")
11 private Long memberId;
12
13 @NotBlank(message = "【名字】不能为空")
14 private String name;
15
16 @NotBlank(message = "【身份证】不能为空")
17 private String idCard;
18
19 @NotBlank(message = "【旅客类型】不能为空")
20 private String type;
21
22 private Date createTime;
23
24 private Date updateTime;
25
26 public Long getId() {
27 return id;
28 }
29
30 public void setId(Long id) {
31 this.id = id;
32 }
33
34 public Long getMemberId() {
35 return memberId;
36 }
37
38 public void setMemberId(Long memberId) {
39 this.memberId = memberId;
40 }
41
42 public String getName() {
43 return name;
44 }
45
46 public void setName(String name) {
47 this.name = name;
48 }
49
50 public String getIdCard() {
51 return idCard;
52 }
53
54 public void setIdCard(String idCard) {
55 this.idCard = idCard;
56 }
57
58 public String getType() {
59 return type;
60 }
61
62 public void setType(String type) {
63 this.type = type;
64 }
65
66 public Date getCreateTime() {
67 return createTime;
68 }
69
70 public void setCreateTime(Date createTime) {
71 this.createTime = createTime;
72 }
73
74 public Date getUpdateTime() {
75 return updateTime;
76 }
77
78 public void setUpdateTime(Date updateTime) {
79 this.updateTime = updateTime;
80 }
81
82 @Override
83 public String toString() {
84 StringBuilder sb = new StringBuilder();
85 sb.append(getClass().getSimpleName());
86 sb.append(" [");
87 sb.append("Hash = ").append(hashCode());
88 sb.append(", id=").append(id);
89 sb.append(", memberId=").append(memberId);
90 sb.append(", name=").append(name);
91 sb.append(", idCard=").append(idCard);
92 sb.append(", type=").append(type);
93 sb.append(", createTime=").append(createTime);
94 sb.append(", updateTime=").append(updateTime);
95 sb.append("]");
96 return sb.toString();
97 }
98 }
PassengerSaveReq.java
在service层新建save保存方法,用于在数据库保存Passenger信息。
1 package com.zihans.train.member.service;
2
3 import cn.hutool.core.bean.BeanUtil;
4 import cn.hutool.core.date.DateTime;
5 import com.zihans.train.common.util.SnowUtil;
6 import com.zihans.train.member.domain.Passenger;
7 import com.zihans.train.member.mapper.PassengerMapper;
8 import com.zihans.train.member.req.PassengerSaveReq;
9 import jakarta.annotation.Resource;
10 import org.springframework.stereotype.Service;
11
12 @Service
13 public class PassengerService {
14
15 @Resource
16 private PassengerMapper passengerMapper;
17
18 public void save(PassengerSaveReq req) {
19 DateTime now = DateTime.now();
20 Passenger passenger = BeanUtil.copyProperties(req, Passenger.class);
21 passenger.setId(SnowUtil.getSnowflakeNextId());
22 passenger.setCreateTime(now);
23 passenger.setUpdateTime(now);
24 passengerMapper.insert(passenger);
25 }
26 }
PassengerService.java
controller
1 package com.zihans.train.member.controller;
2
3
4 import com.zihans.train.common.resp.CommonResp;
5 import com.zihans.train.member.req.PassengerSaveReq;
6 import com.zihans.train.member.service.PassengerService;
7 import jakarta.annotation.Resource;
8 import jakarta.validation.Valid;
9 import org.springframework.web.bind.annotation.PostMapping;
10 import org.springframework.web.bind.annotation.RequestBody;
11 import org.springframework.web.bind.annotation.RequestMapping;
12 import org.springframework.web.bind.annotation.RestController;
13
14 @RestController
15 @RequestMapping("/passenger")
16 public class PassengerController {
17
18 @Resource
19 private PassengerService passengerService;
20
21 @PostMapping("/save")
22 public CommonResp<Object> save(@Valid @RequestBody PassengerSaveReq req) {
23 passengerService.save(req);
24 return new CommonResp<>();
25 }
26
27 }
PassengerController.java
如果使用gateway拦截器登录,每次请求都需要手动增加token,很不方便,可以用HTTPClient内置的全局变量返回token。
login请求时添加将token保存在全局变量。
> {%
client.log(JSON.stringify(response.body));
client.log(JSON.stringify(response.body.content.token));
client.global.set("token", response.body.content.token);
%}
读取
POST http://localhost:8001/member/passenger/save
Content-Type: application/json
token: {{token}}
{
"memberId": "1",
"name": "test",
"idCard": "123321",
"type": "1"
}
功能实现流程:
ThreadLocal可以方便线程内,多个方法传递参数。
本节方案:在接口入口获取会员信息,并放到线程本地变量,则在controller、service都可直接从线程本地变量获取会员信息
之前保存passenger需要前端传入member_id,这个不应该直接传递,要从token中拿,可以使用拦截器来实现。
gateway的拦截器只在gateway有效,而且gateway只是校验,并没有取东西,需要在member中添加拦截器,在controller处进行拦截。
拦截器是通用的,很多地方都要member_id,所以放在common模块。
common中新增拦截器MemberInterceptor拦截token。
1 package com.zihans.train.common.interceptor;
2
3
4 import cn.hutool.core.util.StrUtil;
5 import cn.hutool.json.JSONObject;
6 import cn.hutool.json.JSONUtil;
7 import com.zihans.train.common.context.LoginMemberContext;
8 import com.zihans.train.common.resp.MemberLoginResp;
9 import com.zihans.train.common.util.JwtUtil;
10 import jakarta.servlet.http.HttpServletRequest;
11 import jakarta.servlet.http.HttpServletResponse;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14 import org.springframework.stereotype.Component;
15 import org.springframework.web.servlet.HandlerInterceptor;
16
17 /**
18 * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印
19 */
20 @Component
21 public class MemberInterceptor implements HandlerInterceptor {
22
23 private static final Logger LOG = LoggerFactory.getLogger(MemberInterceptor.class);
24
25 @Override
26 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
27 //获取header的token参数
28 String token = request.getHeader("token");
29 if (StrUtil.isNotBlank(token)) {
30 LOG.info("获取会员登录token:{}", token);
31 JSONObject loginMember = JwtUtil.getJSONObject(token);
32 LOG.info("当前登录会员:{}", loginMember);
33 MemberLoginResp member = JSONUtil.toBean(loginMember, MemberLoginResp.class);
34 LoginMemberContext.setMember(member);
35 }
36 return true;
37 }
38
39 }
MemberInterceptor.java
LoginMemberContext.java上下文来保存和使用本地变量。
1 package com.zihans.train.common.context;
2
3
4 import com.zihans.train.common.resp.MemberLoginResp;
5 import org.slf4j.Logger;
6 import org.slf4j.LoggerFactory;
7
8 public class LoginMemberContext {
9 private static final Logger LOG = LoggerFactory.getLogger(LoginMemberContext.class);
10
11 private static ThreadLocal<MemberLoginResp> member = new ThreadLocal<>();
12
13 public static MemberLoginResp getMember() {
14 return member.get();
15 }
16
17 public static void setMember(MemberLoginResp member) {
18 LoginMemberContext.member.set(member);
19 }
20
21 public static Long getId() {
22 try {
23 return member.get().getId();
24 } catch (Exception e) {
25 LOG.error("获取登录会员信息异常", e);
26 throw e;
27 }
28 }
29
30 }
LoginMemberContext.java
需要配置让拦截器生效。member模块新增SpringMvcConfig实现WebMvcConfigurer接口,使拦截器生效。
1 package com.zihans.train.member.config;
2
3 import com.zihans.train.common.interceptor.MemberInterceptor;
4 import jakarta.annotation.Resource;
5 import org.springframework.context.annotation.Configuration;
6 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
7 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8
9 @Configuration
10 public class SpringMvcConfig implements WebMvcConfigurer {
11
12 @Resource
13 MemberInterceptor memberInterceptor;
14
15 @Override
16 public void addInterceptors(InterceptorRegistry registry) {
17 registry.addInterceptor(memberInterceptor)
18 .addPathPatterns("/**")
19 .excludePathPatterns(
20 "/member/hello",
21 "/member/member/send-code",
22 "/member/member/login"
23 );
24 }
25 }
SpringMvcConfig.java
1 package com.zihans.train.member.resp;
2
3 public class MemberLoginResp {
4 private Long id;
5
6 private String mobile;
7
8 private String token;
9
10 public String getToken() {
11 return token;
12 }
13
14 public void setToken(String token) {
15 this.token = token;
16 }
17
18 public Long getId() {
19 return id;
20 }
21
22 public void setId(Long id) {
23 this.id = id;
24 }
25
26 public String getMobile() {
27 return mobile;
28 }
29
30 public void setMobile(String mobile) {
31 this.mobile = mobile;
32 }
33
34 @Override
35 public String toString() {
36 final StringBuffer sb = new StringBuffer("MemberLoginResp{");
37 sb.append("id=").append(id);
38 sb.append(", mobile='").append(mobile).append('\'');
39 sb.append(", token='").append(token).append('\'');
40 sb.append('}');
41 return sb.toString();
42 }
43 }
MemberLoginResp.java
这时,就可以在PassengerService中通过拦截器获得member_id。
1 package com.zihans.train.member.service;
2
3 import cn.hutool.core.bean.BeanUtil;
4 import cn.hutool.core.date.DateTime;
5 import com.zihans.train.common.context.LoginMemberContext;
6 import com.zihans.train.common.util.SnowUtil;
7 import com.zihans.train.member.domain.Passenger;
8 import com.zihans.train.member.mapper.PassengerMapper;
9 import com.zihans.train.member.req.PassengerSaveReq;
10 import jakarta.annotation.Resource;
11 import org.springframework.stereotype.Service;
12
13 @Service
14 public class PassengerService {
15
16 @Resource
17 private PassengerMapper passengerMapper;
18
19 public void save(PassengerSaveReq req) {
20 DateTime now = DateTime.now();
21 Passenger passenger = BeanUtil.copyProperties(req, Passenger.class);
22 passenger.setMemberId(LoginMemberContext.getId());
23 passenger.setId(SnowUtil.getSnowflakeNextId());
24 passenger.setCreateTime(now);
25 passenger.setUpdateTime(now);
26 passengerMapper.insert(passenger);
27 }
28 }
PassengerService.java
测试
POST http://localhost:8001/member/passenger/save
Content-Type: application/json
token: {{token}}
{
"name": "test",
"idCard": "123321",
"type": "1"
}
此时拦截器的日志并没有打印,因为拦截器发生在AOP之前,新建一个日志拦截器类,专门用来打印日志,并将该拦截器注入相应模块。
1 package com.zihans.train.common.interceptor;
2
3 import cn.hutool.core.util.RandomUtil;
4 import jakarta.servlet.http.HttpServletRequest;
5 import jakarta.servlet.http.HttpServletResponse;
6 import org.slf4j.MDC;
7 import org.springframework.stereotype.Component;
8 import org.springframework.web.servlet.HandlerInterceptor;
9
10 /**
11 * 日志拦截器
12 */
13 @Component
14 public class LogInterceptor implements HandlerInterceptor {
15
16 @Override
17 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
18 // 增加日志流水号
19 MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3));
20 return true;
21 }
22
23 }
LoginInterceptor.java
新增views/main/welcome.vue制作欢迎页面。
1 <template>
2 <h1>欢迎使用模拟12306售票系统</h1>
3 </template>
4 <script>
5 import { defineComponent } from 'vue';
6
7 export default defineComponent({
8 setup() {
9 return {
10 };
11 },
12 });
13 </script>
14 <style>
15 </style>
welcome.vue
router/index.js中增加二级路由(children中的welcome),也就是一级路由"/"+"welcome"。
1 import { createRouter, createWebHistory } from 'vue-router'
2 import store from "@/store";
3 import {notification} from "ant-design-vue";
4
5 const routes = [
6 {
7 path: '/login',
8 component: () => import('../views/login.vue')
9 },
10 {
11 path: '/',
12 component: () => import('../views/main.vue'),
13 meta: {
14 loginRequire: true
15 },
16 children: [{
17 path: 'welcome',
18 component: () => import('../views/main/welcome.vue'),
19 }]
20 },
21 {
22 path: '',
23 redirect: '/welcome'
24 },
25 ]
26
27 const router = createRouter({
28 history: createWebHistory(process.env.BASE_URL),
29 routes
30 })
31
32 // 路由登录拦截
33 router.beforeEach((to, from, next) => {
34 // 要不要对meta.loginRequire属性做监控拦截
35 if (to.matched.some(function (item) {
36 console.log(item, "是否需要登录校验:", item.meta.loginRequire || false);
37 return item.meta.loginRequire
38 })) {
39 const _member = store.state.member;
40 console.log("页面登录校验开始:", _member);
41 if (!_member.token) {
42 console.log("用户未登录或登录超时!");
43 notification.error({ description: "未登录或登录超时" });
44 next('/login');
45 } else {
46 next();
47 }
48 } else {
49 next();
50 }
51 });
52
53 export default router
index.js
一级路由到main.vue,二级路由到welcome.vue。
1 <template>
2 <a-layout id="components-layout-demo-top-side-2">
3 <the-header-view></the-header-view>
4 <a-layout>
5 <the-sider-view></the-sider-view>
6 <a-layout-content
7 :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
8 >
9 <router-view></router-view>
10 </a-layout-content>
11 </a-layout>
12 </a-layout>
13 </template>
14 <script>
15 import { defineComponent } from 'vue';
16 import TheHeaderView from "@/components/the-header";
17 import TheSiderView from "@/components/the-sider";
18
19 export default defineComponent({
20 components: {
21 TheSiderView,
22 TheHeaderView,
23 },
24 setup() {
25 return {
26 };
27 },
28 });
29 </script>
30 <style>
31 #components-layout-demo-top-side-2 .logo {
32 float: left;
33 width: 120px;
34 height: 31px;
35 margin: 16px 24px 16px 0;
36 background: rgba(255, 255, 255, 0.3);
37 }
38
39 .ant-row-rtl #components-layout-demo-top-side-2 .logo {
40 float: right;
41 margin: 16px 0 16px 24px;
42 }
43
44 .site-layout-background {
45 background: #fff;
46 }
47 </style>
main.vue
login.vue登陆成功直接跳到welcome页。
1 <template>
2 <a-row class="login">
3 <a-col :span="8" :offset="8" class="login-main">
4 <h1 style="text-align: center"><car-two-tone /> 模拟12306售票系统</h1>
5 <a-form
6 :model="loginForm"
7 name="basic"
8 autocomplete="off"
9 >
10 <a-form-item
11 label=""
12 name="mobile"
13 :rules="[{ required: true, message: '请输入手机号!' }]"
14 >
15 <a-input v-model:value="loginForm.mobile" placeholder="手机号"/>
16 </a-form-item>
17
18 <a-form-item
19 label=""
20 name="code"
21 :rules="[{ required: true, message: '请输入验证码!' }]"
22 >
23 <a-input v-model:value="loginForm.code">
24 <template #addonAfter>
25 <a @click="sendCode">获取验证码</a>
26 </template>
27 </a-input>
28 <!--<a-input v-model:value="loginForm.code" placeholder="验证码"/>-->
29 </a-form-item>
30
31 <a-form-item>
32 <a-button type="primary" block @click="login">登录</a-button>
33 </a-form-item>
34
35 </a-form>
36 </a-col>
37 </a-row>
38 </template>
39
40 <script>
41 import { defineComponent, reactive } from 'vue';
42 import axios from 'axios';
43 import { notification } from 'ant-design-vue';
44 import { useRouter } from 'vue-router'
45 import store from "@/store";
46
47 export default defineComponent({
48 name: "login-view",
49 setup() {
50 const router = useRouter();
51
52 const loginForm = reactive({
53 mobile: '13000000000',
54 code: '',
55 });
56
57 const sendCode = () => {
58 axios.post("/member/member/send-code", {
59 mobile: loginForm.mobile
60 }).then(response => {
61 let data = response.data;
62 if (data.success) {
63 notification.success({ description: '发送验证码成功!' });
64 loginForm.code = "8888";
65 } else {
66 notification.error({ description: data.message });
67 }
68 });
69 };
70
71 const login = () => {
72 axios.post("/member/member/login", loginForm).then((response) => {
73 let data = response.data;
74 if (data.success) {
75 notification.success({ description: '登录成功!' });
76 // 登录成功,跳到控台主页
77 router.push("/welcome");
78 store.commit("setMember", data.content);
79 } else {
80 notification.error({ description: data.message });
81 }
82 })
83 };
84
85 return {
86 loginForm,
87 sendCode,
88 login
89 };
90 },
91 });
92 </script>
93
94 <style>
95 .login-main h1 {
96 font-size: 25px;
97 font-weight: bold;
98 }
99 .login-main {
100 margin-top: 100px;
101 padding: 30px 30px 20px;
102 border: 2px solid grey;
103 border-radius: 10px;
104 background-color: #fcfcfc;
105 }
106 </style>
login.vue
新增乘车人管理页面
1 <template>
2 <h1>乘车人管理</h1>
3 </template>
4 <script>
5 import { defineComponent } from 'vue';
6
7 export default defineComponent({
8 setup() {
9 return {
10 };
11 },
12 });
13 </script>
14 <style>
15 </style>
passenger.vue
index.js增加passenger二级路由(目前只能手打进入)
1 import { createRouter, createWebHistory } from 'vue-router'
2 import store from "@/store";
3 import {notification} from "ant-design-vue";
4
5 const routes = [{
6 path: '/login',
7 component: () => import('../views/login.vue')
8 }, {
9 path: '/',
10 component: () => import('../views/main.vue'),
11 meta: {
12 loginRequire: true
13 },
14 children: [{
15 path: 'welcome',
16 component: () => import('../views/main/welcome.vue'),
17 }, {
18 path: 'passenger',
19 component: () => import('../views/main/passenger.vue'),
20 }]
21 }, {
22 path: '',
23 redirect: '/welcome'
24 }];
25
26 const router = createRouter({
27 history: createWebHistory(process.env.BASE_URL),
28 routes
29 })
30
31 // 路由登录拦截
32 router.beforeEach((to, from, next) => {
33 // 要不要对meta.loginRequire属性做监控拦截
34 if (to.matched.some(function (item) {
35 console.log(item, "是否需要登录校验:", item.meta.loginRequire || false);
36 return item.meta.loginRequire
37 })) {
38 const _member = store.state.member;
39 console.log("页面登录校验开始:", _member);
40 if (!_member.token) {
41 console.log("用户未登录或登录超时!");
42 notification.error({ description: "未登录或登录超时" });
43 next('/login');
44 } else {
45 next();
46 }
47 } else {
48 next();
49 }
50 });
51
52 export default router
index.js
修改菜单,实现页面切换,实现菜单同步激活。
1 <template>
2 <a-layout-header class="header">
3 <div class="logo" />
4 <div style="float: right; color: white;">
5 您好:{{member.mobile}}
6 <router-link to="/login" style="color: white;">
7 退出登录
8 </router-link>
9 </div>
10 <a-menu
11 v-model:selectedKeys="selectedKeys"
12 theme="dark"
13 mode="horizontal"
14 :style="{ lineHeight: '64px' }"
15 >
16 <a-menu-item key="/welcome">
17 <router-link to="/welcome">
18 <coffee-outlined /> 欢迎
19 </router-link>
20 </a-menu-item>
21 <a-menu-item key="/passenger">
22 <router-link to="/passenger">
23 <user-outlined /> 乘车人管理
24 </router-link>
25 </a-menu-item>
26 </a-menu>
27 </a-layout-header>
28 </template>
29
30 <script>
31 import {defineComponent, ref, watch} from 'vue';
32 import store from "@/store";
33 import router from '@/router'
34
35 export default defineComponent({
36 name: "the-header-view",
37 setup() {
38 let member = store.state.member;
39 const selectedKeys = ref([]);
40
41 watch(() => router.currentRoute.value.path, (newValue) => {
42 console.log('watch', newValue);
43 selectedKeys.value = [];
44 selectedKeys.value.push(newValue);
45 }, {immediate: true});
46 return {
47 member,
48 selectedKeys
49 };
50 },
51 });
52 </script>
53
54 <!-- Add "scoped" attribute to limit CSS to this component only -->
55 <style scoped>
56
57 </style>
the-header.vue
1 <template>
2 <a-layout-sider width="200" style="background: #fff">
3 <a-menu
4 v-model:selectedKeys="selectedKeys"
5 mode="inline"
6 :style="{ height: '100%', borderRight: 0 }"
7 >
8 <a-menu-item key="/welcome">
9 <router-link to="/welcome">
10 <coffee-outlined /> 欢迎
11 </router-link>
12 </a-menu-item>
13 <a-menu-item key="/passenger">
14 <router-link to="/passenger">
15 <user-outlined /> 乘车人管理
16 </router-link>
17 </a-menu-item>
18 </a-menu>
19 </a-layout-sider>
20 </template>
21
22 <script>
23 import {defineComponent, ref, watch} from 'vue';
24 import router from "@/router";
25
26 export default defineComponent({
27 name: "the-sider-view",
28 setup() {
29 const selectedKeys = ref([]);
30
31 watch(() => router.currentRoute.value.path, (newValue) => {
32 console.log('watch', newValue);
33 selectedKeys.value = [];
34 selectedKeys.value.push(newValue);
35 }, {immediate: true});
36 return {
37 selectedKeys
38 };
39 },
40 });
41 </script>
42
43 <!-- Add "scoped" attribute to limit CSS to this component only -->
44 <style scoped>
45
46 </style>
the-side.vue
乘车人新增界面开发,增加【新增】按钮,点击弹出Modal
1 <template>
2 <a-button type="primary" @click="showModal">新增</a-button>
3 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk">
4 <p>Some contents...</p>
5 <p>Some contents...</p>
6 <p>Some contents...</p>
7 </a-modal>
8 </template>
9 <script>
10 import { defineComponent, ref } from 'vue';
11
12 export default defineComponent({
13 setup() {
14 const visible = ref(false);
15
16 const showModal = () => {
17 visible.value = true;
18 };
19
20 const handleOk = e => {
21 console.log(e);
22 visible.value = false;
23 };
24 return {
25 visible,
26 showModal,
27 handleOk,
28 };
29 },
30 });
31 </script>
32 <style>
33 </style>
passenger.vue
乘车人新增界面开发,增加乘车人表单
1 <template>
2 <a-button type="primary" @click="showModal">新增</a-button>
3 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
4 ok-text="确认" cancel-text="取消">
5 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
6 <a-form-item label="姓名">
7 <a-input v-model:value="passenger.name" />
8 </a-form-item>
9 <a-form-item label="身份证">
10 <a-input v-model:value="passenger.idCard" />
11 </a-form-item>
12 <a-form-item label="类型">
13 <a-select v-model:value="passenger.type">
14 <a-select-option value="1">成人</a-select-option>
15 <a-select-option value="2">儿童</a-select-option>
16 <a-select-option value="3">学生</a-select-option>
17 </a-select>
18 </a-form-item>
19 </a-form>
20 </a-modal>
21 </template>
22 <script>
23 import { defineComponent, ref, reactive } from 'vue';
24
25 export default defineComponent({
26 setup() {
27 const visible = ref(false);
28 const passenger = reactive({
29 id: undefined,
30 memberId: undefined,
31 name: undefined,
32 idCard: undefined,
33 type: undefined,
34 createTime: undefined,
35 updateTime: undefined,
36 });
37
38 const showModal = () => {
39 visible.value = true;
40 };
41
42 const handleOk = e => {
43 console.log(e);
44 visible.value = false;
45 };
46 return {
47 passenger,
48 visible,
49 showModal,
50 handleOk,
51 };
52 },
53 });
54 </script>
55 <style>
56 </style>
passenger.vue
乘车人新增界面开发,完成保存功能
1 <template>
2 <a-button type="primary" @click="showModal">新增</a-button>
3 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
4 ok-text="确认" cancel-text="取消">
5 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
6 <a-form-item label="姓名">
7 <a-input v-model:value="passenger.name" />
8 </a-form-item>
9 <a-form-item label="身份证">
10 <a-input v-model:value="passenger.idCard" />
11 </a-form-item>
12 <a-form-item label="类型">
13 <a-select v-model:value="passenger.type">
14 <a-select-option value="1">成人</a-select-option>
15 <a-select-option value="2">儿童</a-select-option>
16 <a-select-option value="3">学生</a-select-option>
17 </a-select>
18 </a-form-item>
19 </a-form>
20 </a-modal>
21 </template>
22 <script>
23 import { defineComponent, ref, reactive } from 'vue';
24 import {notification} from "ant-design-vue";
25 import axios from "axios";
26
27 export default defineComponent({
28 setup() {
29 const visible = ref(false);
30 const passenger = reactive({
31 id: undefined,
32 memberId: undefined,
33 name: undefined,
34 idCard: undefined,
35 type: undefined,
36 createTime: undefined,
37 updateTime: undefined,
38 });
39
40 const showModal = () => {
41 visible.value = true;
42 };
43
44 const handleOk = () => {
45 axios.post("/member/passenger/save", passenger).then((response) => {
46 let data = response.data;
47 if (data.success) {
48 notification.success({description: "保存成功!"});
49 visible.value = false;
50 } else {
51 notification.error({description: data.message});
52 }
53 });
54 };
55
56 return {
57 passenger,
58 visible,
59 showModal,
60 handleOk,
61 };
62 },
63 });
64 </script>
65 <style>
66 </style>
passenger.vue
所有的列表查询接口,都要注意查询的数据量,不能因为查询量太大,把内存打爆,也不能因为数据量太大,而把带宽打爆,特别注意是否有大字段,如多媒体字段。
控台接口、会员接口、第三方接口等,不同的接口用不同的前缀,比如控台前面是/admin/xxx,第三方全部是/api/xxx,做接口隔离不要混用。
增加乘车人列表查询接口,只能查看当前全员自己添加的乘客
1 package com.zihans.train.member.resp;
2
3 import java.util.Date;
4
5 public class PassengerQueryResp {
6 private Long id;
7
8 private Long memberId;
9
10 private String name;
11
12 private String idCard;
13
14 private String type;
15
16 private Date createTime;
17
18 private Date updateTime;
19
20 public Long getId() {
21 return id;
22 }
23
24 public void setId(Long id) {
25 this.id = id;
26 }
27
28 public Long getMemberId() {
29 return memberId;
30 }
31
32 public void setMemberId(Long memberId) {
33 this.memberId = memberId;
34 }
35
36 public String getName() {
37 return name;
38 }
39
40 public void setName(String name) {
41 this.name = name;
42 }
43
44 public String getIdCard() {
45 return idCard;
46 }
47
48 public void setIdCard(String idCard) {
49 this.idCard = idCard;
50 }
51
52 public String getType() {
53 return type;
54 }
55
56 public void setType(String type) {
57 this.type = type;
58 }
59
60 public Date getCreateTime() {
61 return createTime;
62 }
63
64 public void setCreateTime(Date createTime) {
65 this.createTime = createTime;
66 }
67
68 public Date getUpdateTime() {
69 return updateTime;
70 }
71
72 public void setUpdateTime(Date updateTime) {
73 this.updateTime = updateTime;
74 }
75
76 @Override
77 public String toString() {
78 StringBuilder sb = new StringBuilder();
79 sb.append(getClass().getSimpleName());
80 sb.append(" [");
81 sb.append("Hash = ").append(hashCode());
82 sb.append(", id=").append(id);
83 sb.append(", memberId=").append(memberId);
84 sb.append(", name=").append(name);
85 sb.append(", idCard=").append(idCard);
86 sb.append(", type=").append(type);
87 sb.append(", createTime=").append(createTime);
88 sb.append(", updateTime=").append(updateTime);
89 sb.append("]");
90 return sb.toString();
91 }
92 }
PassengerQueryReq.java
1 package com.zihans.train.member.resp;
2
3 import java.util.Date;
4
5 public class PassengerQueryResp {
6 private Long id;
7
8 private Long memberId;
9
10 private String name;
11
12 private String idCard;
13
14 private String type;
15
16 private Date createTime;
17
18 private Date updateTime;
19
20 public Long getId() {
21 return id;
22 }
23
24 public void setId(Long id) {
25 this.id = id;
26 }
27
28 public Long getMemberId() {
29 return memberId;
30 }
31
32 public void setMemberId(Long memberId) {
33 this.memberId = memberId;
34 }
35
36 public String getName() {
37 return name;
38 }
39
40 public void setName(String name) {
41 this.name = name;
42 }
43
44 public String getIdCard() {
45 return idCard;
46 }
47
48 public void setIdCard(String idCard) {
49 this.idCard = idCard;
50 }
51
52 public String getType() {
53 return type;
54 }
55
56 public void setType(String type) {
57 this.type = type;
58 }
59
60 public Date getCreateTime() {
61 return createTime;
62 }
63
64 public void setCreateTime(Date createTime) {
65 this.createTime = createTime;
66 }
67
68 public Date getUpdateTime() {
69 return updateTime;
70 }
71
72 public void setUpdateTime(Date updateTime) {
73 this.updateTime = updateTime;
74 }
75
76 @Override
77 public String toString() {
78 StringBuilder sb = new StringBuilder();
79 sb.append(getClass().getSimpleName());
80 sb.append(" [");
81 sb.append("Hash = ").append(hashCode());
82 sb.append(", id=").append(id);
83 sb.append(", memberId=").append(memberId);
84 sb.append(", name=").append(name);
85 sb.append(", idCard=").append(idCard);
86 sb.append(", type=").append(type);
87 sb.append(", createTime=").append(createTime);
88 sb.append(", updateTime=").append(updateTime);
89 sb.append("]");
90 return sb.toString();
91 }
92 }
PassengerQueryResp.java
PassengerService新增查询方法
public List<PassengerQueryResp> queryList(PassengerQueryReq req) {
PassengerExample passengerExample = new PassengerExample();
PassengerExample.Criteria criteria = passengerExample.createCriteria();
if (ObjectUtil.isNotNull(req.getMemberId())) {
criteria.andMemberIdEqualTo(req.getMemberId());
}
List<Passenger> passengerList = passengerMapper.selectByExample(passengerExample);
return BeanUtil.copyToList(passengerList, PassengerQueryResp.class);
}
PassengerController也新增查询方法
@GetMapping("/query-list")
public CommonResp<List<PassengerQueryResp>> queryList(@Valid PassengerQueryReq req) {
req.setMemberId(LoginMemberContext.getId());
List<PassengerQueryResp> list = passengerService.queryList(req);
return new CommonResp<>(list);
}
测试
GET http://localhost:8000/member/passenger/query-list
Accept: application/json
token: {{token}}
列表接口要做分页,否则容易因为数据量大而把内存打爆或把带宽打爆。
PageHelper.startPage只对第—条遇到的sql起作用,使用时,要紧跟着查询语句。
父pom新增插件,用来mybatis分页,common模块也使用。
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
在Passenger中执行sql的地方上方插入分页代码(第一页查两条)
PageHelper.startPage(1,2); List<Passenger> passengerList = passengerMapper.selectByExample(passengerExample);
封装分页参数请求
1 package com.zihans.train.common.req;
2
3 import jakarta.validation.constraints.Max;
4 import jakarta.validation.constraints.NotNull;
5
6 public class PageReq {
7
8 @NotNull(message = "【页码】不能为空")
9 private Integer page;
10
11 @NotNull(message = "【每页条数】不能为空")
12 @Max(value = 100, message = "【每页条数】不能超过100")
13 private Integer size;
14
15 public Integer getPage() {
16 return page;
17 }
18
19 public void setPage(Integer page) {
20 this.page = page;
21 }
22
23 public Integer getSize() {
24 return size;
25 }
26
27 public void setSize(Integer size) {
28 this.size = size;
29 }
30
31 @Override
32 public String toString() {
33 return "PageReq{" +
34 "page=" + page +
35 ", size=" + size +
36 '}';
37 }
38 }
PageReq
PassengerQueryReq继承PageReq
public class PassengerQueryReq extends PageReq{
PassengerService修改分页请求参数,跟随url传入
- PageHelper.startPage(2, 2); + PageHelper.startPage(req.getPage(), req.getSize());
查询
GET http://localhost:8000/member/passenger/query-list?page=2&size=200
封装分页返回结果
1 import java.io.Serializable;
2 import java.util.List;
3
4 public class PageResp<T> implements Serializable {
5
6 /**
7 * 总条数
8 */
9 private Long total;
10
11 /**
12 * 当前页的列表
13 */
14 private List<T> list;
15
16 public Long getTotal() {
17 return total;
18 }
19
20 public void setTotal(Long total) {
21 this.total = total;
22 }
23
24 public List<T> getList() {
25 return list;
26 }
27
28 public void setList(List<T> list) {
29 this.list = list;
30 }
31
32 @Override
33 public String toString() {
34 return "PageResp{" +
35 "total=" + total +
36 ", list=" + list +
37 '}';
38 }
39 }
PageResp.java
service层
1 public PageResp<PassengerQueryResp> queryList(PassengerQueryReq req) {
2 PassengerExample passengerExample = new PassengerExample();
3 PassengerExample.Criteria criteria = passengerExample.createCriteria();
4 if (ObjectUtil.isNotNull(req.getMemberId())) {
5 criteria.andMemberIdEqualTo(req.getMemberId());
6 }
7
8 LOG.info("查询页码:{}", req.getPage());
9 LOG.info("每页条数:{}", req.getSize());
10 PageHelper.startPage(req.getPage(), req.getSize());
11 List<Passenger> passengerList = passengerMapper.selectByExample(passengerExample);
12
13 PageInfo<Passenger> pageInfo = new PageInfo<>(passengerList);
14 LOG.info("总行数:{}", pageInfo.getTotal());
15 LOG.info("总页数:{}", pageInfo.getPages());
16
17 List<PassengerQueryResp> list = BeanUtil.copyToList(passengerList, PassengerQueryResp.class);
18
19 PageResp<PassengerQueryResp> pageResp = new PageResp<>();
20 pageResp.setTotal(pageInfo.getTotal());
21 pageResp.setList(list);
22 return pageResp;
23 }
PassengerService.java
controller层
1 @GetMapping("/query-list")
2 public CommonResp<PageResp<PassengerQueryResp>> queryList(@Valid PassengerQueryReq req) {
3 req.setMemberId(LoginMemberContext.getId());
4 PageResp<PassengerQueryResp> list = passengerService.queryList(req);
5 return new CommonResp<>(list);
6 }
PassengerController.java
格式化PassengerQueryResp.java中的日期字段
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
引入表格组件
1 <template>
2 <p>
3 <a-button type="primary" @click="showModal">新增</a-button>
4 </p>
5 <a-table :dataSource="dataSource" :columns="columns" />
6 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
7 ok-text="确认" cancel-text="取消">
8 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
9 <a-form-item label="姓名">
10 <a-input v-model:value="passenger.name" />
11 </a-form-item>
12 <a-form-item label="身份证">
13 <a-input v-model:value="passenger.idCard" />
14 </a-form-item>
15 <a-form-item label="类型">
16 <a-select v-model:value="passenger.type">
17 <a-select-option value="1">成人</a-select-option>
18 <a-select-option value="2">儿童</a-select-option>
19 <a-select-option value="3">学生</a-select-option>
20 </a-select>
21 </a-form-item>
22 </a-form>
23 </a-modal>
24 </template>
25 <script>
26 import { defineComponent, ref, reactive } from 'vue';
27 import {notification} from "ant-design-vue";
28 import axios from "axios";
29
30 export default defineComponent({
31 setup() {
32 const visible = ref(false);
33 const passenger = reactive({
34 id: undefined,
35 memberId: undefined,
36 name: undefined,
37 idCard: undefined,
38 type: undefined,
39 createTime: undefined,
40 updateTime: undefined,
41 });
42 const dataSource = [{
43 key: '1',
44 name: '胡彦斌',
45 age: 32,
46 address: '西湖区湖底公园1号',
47 }, {
48 key: '2',
49 name: '胡彦祖',
50 age: 42,
51 address: '西湖区湖底公园1号',
52 }];
53 const columns = [{
54 title: '姓名',
55 dataIndex: 'name',
56 key: 'name',
57 }, {
58 title: '年龄',
59 dataIndex: 'age',
60 key: 'age',
61 }, {
62 title: '住址',
63 dataIndex: 'address',
64 key: 'address',
65 }];
66
67 const showModal = () => {
68 visible.value = true;
69 };
70
71 const handleOk = () => {
72 axios.post("/member/passenger/save", passenger).then((response) => {
73 let data = response.data;
74 if (data.success) {
75 notification.success({description: "保存成功!"});
76 visible.value = false;
77 } else {
78 notification.error({description: data.message});
79 }
80 });
81 };
82
83 return {
84 passenger,
85 visible,
86 showModal,
87 handleOk,
88 dataSource,
89 columns
90 };
91 },
92 });
93 </script>
94 <style>
95 </style>
passenger.vue
使用axios调用后端查询接口
1 <template>
2 <p>
3 <a-button type="primary" @click="showModal">新增</a-button>
4 </p>
5 <a-table :dataSource="passengers" :columns="columns" />
6 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
7 ok-text="确认" cancel-text="取消">
8 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
9 <a-form-item label="姓名">
10 <a-input v-model:value="passenger.name" />
11 </a-form-item>
12 <a-form-item label="身份证">
13 <a-input v-model:value="passenger.idCard" />
14 </a-form-item>
15 <a-form-item label="类型">
16 <a-select v-model:value="passenger.type">
17 <a-select-option value="1">成人</a-select-option>
18 <a-select-option value="2">儿童</a-select-option>
19 <a-select-option value="3">学生</a-select-option>
20 </a-select>
21 </a-form-item>
22 </a-form>
23 </a-modal>
24 </template>
25 <script>
26 import { defineComponent, ref, reactive, onMounted } from 'vue';
27 import {notification} from "ant-design-vue";
28 import axios from "axios";
29
30 export default defineComponent({
31 setup() {
32 const visible = ref(false);
33 const passenger = reactive({
34 id: undefined,
35 memberId: undefined,
36 name: undefined,
37 idCard: undefined,
38 type: undefined,
39 createTime: undefined,
40 updateTime: undefined,
41 });
42 const passengers = ref([]);
43 const columns = [{
44 title: '姓名',
45 dataIndex: 'name',
46 key: 'name',
47 }, {
48 title: '身份证',
49 dataIndex: 'idCard',
50 key: 'idCard',
51 }, {
52 title: '类型',
53 dataIndex: 'type',
54 key: 'type',
55 }];
56
57 const showModal = () => {
58 visible.value = true;
59 };
60
61 const handleOk = () => {
62 axios.post("/member/passenger/save", passenger).then((response) => {
63 let data = response.data;
64 if (data.success) {
65 notification.success({description: "保存成功!"});
66 visible.value = false;
67 } else {
68 notification.error({description: data.message});
69 }
70 });
71 };
72
73 const handleQuery = (param) => {
74 axios.get("/member/passenger/query-list", {
75 params: {
76 page: param.page,
77 size: param.size
78 }
79 }).then((response) => {
80 let data = response.data;
81 if (data.success) {
82 passengers.value = data.content.list;
83 } else {
84 notification.error({description: data.message});
85 }
86 });
87 };
88
89 onMounted(() => {
90 handleQuery({
91 page: 1,
92 size: 2
93 });
94 });
95
96 return {
97 passenger,
98 visible,
99 showModal,
100 handleOk,
101 passengers,
102 columns
103 };
104 },
105 });
106 </script>
107 <style>
108 </style>
passenger.vue
显示分页页码
1 <template>
2 <p>
3 <a-button type="primary" @click="showModal">新增</a-button>
4 </p>
5 <a-table :dataSource="passengers" :columns="columns" :pagination="pagination"/>
6 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
7 ok-text="确认" cancel-text="取消">
8 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
9 <a-form-item label="姓名">
10 <a-input v-model:value="passenger.name" />
11 </a-form-item>
12 <a-form-item label="身份证">
13 <a-input v-model:value="passenger.idCard" />
14 </a-form-item>
15 <a-form-item label="类型">
16 <a-select v-model:value="passenger.type">
17 <a-select-option value="1">成人</a-select-option>
18 <a-select-option value="2">儿童</a-select-option>
19 <a-select-option value="3">学生</a-select-option>
20 </a-select>
21 </a-form-item>
22 </a-form>
23 </a-modal>
24 </template>
25 <script>
26 import { defineComponent, ref, reactive, onMounted } from 'vue';
27 import {notification} from "ant-design-vue";
28 import axios from "axios";
29
30 export default defineComponent({
31 setup() {
32 const visible = ref(false);
33 const passenger = reactive({
34 id: undefined,
35 memberId: undefined,
36 name: undefined,
37 idCard: undefined,
38 type: undefined,
39 createTime: undefined,
40 updateTime: undefined,
41 });
42 const passengers = ref([]);
43 // 分页的三个属性名是固定的
44 const pagination = reactive({
45 total: 0,
46 current: 1,
47 pageSize: 2,
48 });
49 const columns = [{
50 title: '姓名',
51 dataIndex: 'name',
52 key: 'name',
53 }, {
54 title: '身份证',
55 dataIndex: 'idCard',
56 key: 'idCard',
57 }, {
58 title: '类型',
59 dataIndex: 'type',
60 key: 'type',
61 }];
62
63 const showModal = () => {
64 visible.value = true;
65 };
66
67 const handleOk = () => {
68 axios.post("/member/passenger/save", passenger).then((response) => {
69 let data = response.data;
70 if (data.success) {
71 notification.success({description: "保存成功!"});
72 visible.value = false;
73 } else {
74 notification.error({description: data.message});
75 }
76 });
77 };
78
79 const handleQuery = (param) => {
80 axios.get("/member/passenger/query-list", {
81 params: {
82 page: param.page,
83 size: param.size
84 }
85 }).then((response) => {
86 let data = response.data;
87 if (data.success) {
88 passengers.value = data.content.list;
89 pagination.total = data.content.total;
90 } else {
91 notification.error({description: data.message});
92 }
93 });
94 };
95
96 onMounted(() => {
97 handleQuery({
98 page: 1,
99 size: 2
100 });
101 });
102
103 return {
104 passenger,
105 visible,
106 showModal,
107 handleOk,
108 passengers,
109 pagination,
110 columns
111 };
112 },
113 });
114 </script>
115 <style>
116 </style>
passenger.vue
增加表格分页点击事件
1 <template>
2 <p>
3 <a-button type="primary" @click="showModal">新增</a-button>
4 </p>
5 <a-table :dataSource="passengers" :columns="columns" :pagination="pagination" @change="handleTableChange"/>
6 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
7 ok-text="确认" cancel-text="取消">
8 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
9 <a-form-item label="姓名">
10 <a-input v-model:value="passenger.name" />
11 </a-form-item>
12 <a-form-item label="身份证">
13 <a-input v-model:value="passenger.idCard" />
14 </a-form-item>
15 <a-form-item label="类型">
16 <a-select v-model:value="passenger.type">
17 <a-select-option value="1">成人</a-select-option>
18 <a-select-option value="2">儿童</a-select-option>
19 <a-select-option value="3">学生</a-select-option>
20 </a-select>
21 </a-form-item>
22 </a-form>
23 </a-modal>
24 </template>
25 <script>
26 import { defineComponent, ref, reactive, onMounted } from 'vue';
27 import {notification} from "ant-design-vue";
28 import axios from "axios";
29
30 export default defineComponent({
31 setup() {
32 const visible = ref(false);
33 const passenger = reactive({
34 id: undefined,
35 memberId: undefined,
36 name: undefined,
37 idCard: undefined,
38 type: undefined,
39 createTime: undefined,
40 updateTime: undefined,
41 });
42 const passengers = ref([]);
43 // 分页的三个属性名是固定的
44 const pagination = reactive({
45 total: 0,
46 current: 1,
47 pageSize: 2,
48 });
49 const columns = [{
50 title: '姓名',
51 dataIndex: 'name',
52 key: 'name',
53 }, {
54 title: '身份证',
55 dataIndex: 'idCard',
56 key: 'idCard',
57 }, {
58 title: '类型',
59 dataIndex: 'type',
60 key: 'type',
61 }];
62
63 const showModal = () => {
64 visible.value = true;
65 };
66
67 const handleOk = () => {
68 axios.post("/member/passenger/save", passenger).then((response) => {
69 let data = response.data;
70 if (data.success) {
71 notification.success({description: "保存成功!"});
72 visible.value = false;
73 } else {
74 notification.error({description: data.message});
75 }
76 });
77 };
78
79 const handleQuery = (param) => {
80 axios.get("/member/passenger/query-list", {
81 params: {
82 page: param.page,
83 size: param.size
84 }
85 }).then((response) => {
86 let data = response.data;
87 if (data.success) {
88 passengers.value = data.content.list;
89 // 设置分页控件的值
90 pagination.current = param.page;
91 pagination.total = data.content.total;
92 } else {
93 notification.error({description: data.message});
94 }
95 });
96 };
97
98 const handleTableChange = (pagination) => {
99 // console.log("看看自带的分页参数都有啥:" + pagination);
100 handleQuery({
101 page: pagination.current,
102 size: pagination.pageSize
103 });
104 };
105
106 onMounted(() => {
107 handleQuery({
108 page: 1,
109 size: pagination.pageSize
110 });
111 });
112
113 return {
114 passenger,
115 visible,
116 showModal,
117 handleOk,
118 passengers,
119 pagination,
120 columns,
121 handleTableChange
122 };
123 },
124 });
125 </script>
126 <style>
127 </style>
passenger.vue
增加刷新按钮,点击查询第1页
1 <template>
2 <p>
3 <a-space>
4 <a-button type="primary" @click="handleQuery()">刷新</a-button>
5 <a-button type="primary" @click="showModal">新增</a-button>
6 </a-space>
7 </p>
8 <a-table :dataSource="passengers" :columns="columns" :pagination="pagination" @change="handleTableChange"/>
9 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
10 ok-text="确认" cancel-text="取消">
11 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
12 <a-form-item label="姓名">
13 <a-input v-model:value="passenger.name" />
14 </a-form-item>
15 <a-form-item label="身份证">
16 <a-input v-model:value="passenger.idCard" />
17 </a-form-item>
18 <a-form-item label="类型">
19 <a-select v-model:value="passenger.type">
20 <a-select-option value="1">成人</a-select-option>
21 <a-select-option value="2">儿童</a-select-option>
22 <a-select-option value="3">学生</a-select-option>
23 </a-select>
24 </a-form-item>
25 </a-form>
26 </a-modal>
27 </template>
28 <script>
29 import { defineComponent, ref, reactive, onMounted } from 'vue';
30 import {notification} from "ant-design-vue";
31 import axios from "axios";
32
33 export default defineComponent({
34 setup() {
35 const visible = ref(false);
36 const passenger = reactive({
37 id: undefined,
38 memberId: undefined,
39 name: undefined,
40 idCard: undefined,
41 type: undefined,
42 createTime: undefined,
43 updateTime: undefined,
44 });
45 const passengers = ref([]);
46 // 分页的三个属性名是固定的
47 const pagination = reactive({
48 total: 0,
49 current: 1,
50 pageSize: 2,
51 });
52 const columns = [{
53 title: '姓名',
54 dataIndex: 'name',
55 key: 'name',
56 }, {
57 title: '身份证',
58 dataIndex: 'idCard',
59 key: 'idCard',
60 }, {
61 title: '类型',
62 dataIndex: 'type',
63 key: 'type',
64 }];
65
66 const showModal = () => {
67 visible.value = true;
68 };
69
70 const handleOk = () => {
71 axios.post("/member/passenger/save", passenger).then((response) => {
72 let data = response.data;
73 if (data.success) {
74 notification.success({description: "保存成功!"});
75 visible.value = false;
76 } else {
77 notification.error({description: data.message});
78 }
79 });
80 };
81
82 const handleQuery = (param) => {
83 if (!param) {
84 param = {
85 page: 1,
86 size: pagination.pageSize
87 };
88 }
89 axios.get("/member/passenger/query-list", {
90 params: {
91 page: param.page,
92 size: param.size
93 }
94 }).then((response) => {
95 let data = response.data;
96 if (data.success) {
97 passengers.value = data.content.list;
98 // 设置分页控件的值
99 pagination.current = param.page;
100 pagination.total = data.content.total;
101 } else {
102 notification.error({description: data.message});
103 }
104 });
105 };
106
107 const handleTableChange = (pagination) => {
108 // console.log("看看自带的分页参数都有啥:" + pagination);
109 handleQuery({
110 page: pagination.current,
111 size: pagination.pageSize
112 });
113 };
114
115 onMounted(() => {
116 handleQuery({
117 page: 1,
118 size: pagination.pageSize
119 });
120 });
121
122 return {
123 passenger,
124 visible,
125 showModal,
126 handleOk,
127 passengers,
128 pagination,
129 columns,
130 handleTableChange,
131 handleQuery
132 };
133 },
134 });
135 </script>
136 <style>
137 </style>
passenger.vue
增加loading效果
1 <template>
2 <p>
3 <a-space>
4 <a-button type="primary" @click="handleQuery()">刷新</a-button>
5 <a-button type="primary" @click="showModal">新增</a-button>
6 </a-space>
7 </p>
8 <a-table :dataSource="passengers"
9 :columns="columns"
10 :pagination="pagination"
11 @change="handleTableChange"
12 :loading="loading"/>
13 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
14 ok-text="确认" cancel-text="取消">
15 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
16 <a-form-item label="姓名">
17 <a-input v-model:value="passenger.name" />
18 </a-form-item>
19 <a-form-item label="身份证">
20 <a-input v-model:value="passenger.idCard" />
21 </a-form-item>
22 <a-form-item label="类型">
23 <a-select v-model:value="passenger.type">
24 <a-select-option value="1">成人</a-select-option>
25 <a-select-option value="2">儿童</a-select-option>
26 <a-select-option value="3">学生</a-select-option>
27 </a-select>
28 </a-form-item>
29 </a-form>
30 </a-modal>
31 </template>
32 <script>
33 import { defineComponent, ref, reactive, onMounted } from 'vue';
34 import {notification} from "ant-design-vue";
35 import axios from "axios";
36
37 export default defineComponent({
38 setup() {
39 const visible = ref(false);
40 const passenger = reactive({
41 id: undefined,
42 memberId: undefined,
43 name: undefined,
44 idCard: undefined,
45 type: undefined,
46 createTime: undefined,
47 updateTime: undefined,
48 });
49 const passengers = ref([]);
50 // 分页的三个属性名是固定的
51 const pagination = reactive({
52 total: 0,
53 current: 1,
54 pageSize: 2,
55 });
56 let loading = ref(false);
57 const columns = [{
58 title: '姓名',
59 dataIndex: 'name',
60 key: 'name',
61 }, {
62 title: '身份证',
63 dataIndex: 'idCard',
64 key: 'idCard',
65 }, {
66 title: '类型',
67 dataIndex: 'type',
68 key: 'type',
69 }];
70
71 const showModal = () => {
72 visible.value = true;
73 };
74
75 const handleOk = () => {
76 axios.post("/member/passenger/save", passenger).then((response) => {
77 let data = response.data;
78 if (data.success) {
79 notification.success({description: "保存成功!"});
80 visible.value = false;
81 } else {
82 notification.error({description: data.message});
83 }
84 });
85 };
86
87 const handleQuery = (param) => {
88 if (!param) {
89 param = {
90 page: 1,
91 size: pagination.pageSize
92 };
93 }
94 loading.value = true;
95 axios.get("/member/passenger/query-list", {
96 params: {
97 page: param.page,
98 size: param.size
99 }
100 }).then((response) => {
101 loading.value = false;
102 let data = response.data;
103 if (data.success) {
104 passengers.value = data.content.list;
105 // 设置分页控件的值
106 pagination.current = param.page;
107 pagination.total = data.content.total;
108 } else {
109 notification.error({description: data.message});
110 }
111 });
112 };
113
114 const handleTableChange = (pagination) => {
115 // console.log("看看自带的分页参数都有啥:" + pagination);
116 handleQuery({
117 page: pagination.current,
118 size: pagination.pageSize
119 });
120 };
121
122 onMounted(() => {
123 handleQuery({
124 page: 1,
125 size: pagination.pageSize
126 });
127 });
128
129 return {
130 passenger,
131 visible,
132 showModal,
133 handleOk,
134 passengers,
135 pagination,
136 columns,
137 handleTableChange,
138 handleQuery,
139 loading
140 };
141 },
142 });
143 </script>
144 <style>
145 </style>
passenger.vue
保存成功后刷新列表
1 <template>
2 <p>
3 <a-space>
4 <a-button type="primary" @click="handleQuery()">刷新</a-button>
5 <a-button type="primary" @click="showModal">新增</a-button>
6 </a-space>
7 </p>
8 <a-table :dataSource="passengers"
9 :columns="columns"
10 :pagination="pagination"
11 @change="handleTableChange"
12 :loading="loading"/>
13 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
14 ok-text="确认" cancel-text="取消">
15 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
16 <a-form-item label="姓名">
17 <a-input v-model:value="passenger.name" />
18 </a-form-item>
19 <a-form-item label="身份证">
20 <a-input v-model:value="passenger.idCard" />
21 </a-form-item>
22 <a-form-item label="类型">
23 <a-select v-model:value="passenger.type">
24 <a-select-option value="1">成人</a-select-option>
25 <a-select-option value="2">儿童</a-select-option>
26 <a-select-option value="3">学生</a-select-option>
27 </a-select>
28 </a-form-item>
29 </a-form>
30 </a-modal>
31 </template>
32 <script>
33 import { defineComponent, ref, reactive, onMounted } from 'vue';
34 import {notification} from "ant-design-vue";
35 import axios from "axios";
36
37 export default defineComponent({
38 setup() {
39 const visible = ref(false);
40 const passenger = reactive({
41 id: undefined,
42 memberId: undefined,
43 name: undefined,
44 idCard: undefined,
45 type: undefined,
46 createTime: undefined,
47 updateTime: undefined,
48 });
49 const passengers = ref([]);
50 // 分页的三个属性名是固定的
51 const pagination = reactive({
52 total: 0,
53 current: 1,
54 pageSize: 2,
55 });
56 let loading = ref(false);
57 const columns = [{
58 title: '姓名',
59 dataIndex: 'name',
60 key: 'name',
61 }, {
62 title: '身份证',
63 dataIndex: 'idCard',
64 key: 'idCard',
65 }, {
66 title: '类型',
67 dataIndex: 'type',
68 key: 'type',
69 }];
70
71 const showModal = () => {
72 visible.value = true;
73 };
74
75 const handleOk = () => {
76 axios.post("/member/passenger/save", passenger).then((response) => {
77 let data = response.data;
78 if (data.success) {
79 notification.success({description: "保存成功!"});
80 visible.value = false;
81 handleQuery({
82 page: pagination.current,
83 size: pagination.pageSize
84 });
85 } else {
86 notification.error({description: data.message});
87 }
88 });
89 };
90
91 const handleQuery = (param) => {
92 if (!param) {
93 param = {
94 page: 1,
95 size: pagination.pageSize
96 };
97 }
98 loading.value = true;
99 axios.get("/member/passenger/query-list", {
100 params: {
101 page: param.page,
102 size: param.size
103 }
104 }).then((response) => {
105 loading.value = false;
106 let data = response.data;
107 if (data.success) {
108 passengers.value = data.content.list;
109 // 设置分页控件的值
110 pagination.current = param.page;
111 pagination.total = data.content.total;
112 } else {
113 notification.error({description: data.message});
114 }
115 });
116 };
117
118 const handleTableChange = (pagination) => {
119 // console.log("看看自带的分页参数都有啥:" + pagination);
120 handleQuery({
121 page: pagination.current,
122 size: pagination.pageSize
123 });
124 };
125
126 onMounted(() => {
127 handleQuery({
128 page: 1,
129 size: pagination.pageSize
130 });
131 });
132
133 return {
134 passenger,
135 visible,
136 showModal,
137 handleOk,
138 passengers,
139 pagination,
140 columns,
141 handleTableChange,
142 handleQuery,
143 loading
144 };
145 },
146 });
147 </script>
148 <style>
149 </style>
passenger.vue
PassengerService.java新增
+ PassengerExample passengerExample = new PassengerExample();
passengerExample.setOrderByClause("id desc");
不同的语言,虽然都有int long等类型,但他们的精度不太一样,在数据传递时需要特别注意精度丢失。
解决方法:将long传成string
两种方法
第一种是统一注解。把许多小的long也转换了,不是很好。会多很多警告。
1 package com.zihans.train.common.config;
2
3 import com.fasterxml.jackson.databind.ObjectMapper;
4 import com.fasterxml.jackson.databind.module.SimpleModule;
5 import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
6 import org.springframework.context.annotation.Bean;
7 import org.springframework.context.annotation.Configuration;
8 import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
9
10 /**
11 * 统一注解,解决前后端交互Long类型精度丢失的问题
12 */
13 @Configuration
14 public class JacksonConfig {
15 @Bean
16 public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
17 ObjectMapper objectMapper = builder.createXmlMapper(false).build();
18 SimpleModule simpleModule = new SimpleModule();
19 simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
20 objectMapper.registerModule(simpleModule);
21 return objectMapper;
22 }
23 }
JacksonConfig.java
第二种在返回结果加注解,用雪花算法生成的大long都加。
public class PassengerQueryResp {
@JsonSerialize(using= ToStringSerializer.class)
private Long id;
@JsonSerialize(using= ToStringSerializer.class)
private Long memberId;
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc