旅游信息管理系统

需求分析

分析各个模块和确定技术选型

  • 用户模块
    • 登录和注册
  • 省份模块
    • 增删改查
    • 使用redis作为mybatis的缓存
  • 景点模块
    • 增删改查
    • 使用redis作为mybatis的缓存
  • 技术选型
    • 前端:vue+axios
    • 后端:springboot + mybatis + mysql + redis

库表设计

1、系统中有哪些表

用户表t_user、省份表t_province、景点表t_place

2、表与表之间的关系

用户表是独立的;省份表与景点表是1:N的关系

3、表中的字段

t_user:id、username、password、email

t_province:id、name、tags、placecounts

t_place:id、name、picpath、hotticket、dimticket、details、provinceid

库:travels

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
create table t_user(
id int(6) primary key auto_increment,
username varchar(20),
password varchar(20),
email varchar(30)
);

create table t_province(
id int(6) primary key auto_increment,
name varchar(20),
tags varchar(80),
placecounts int(4)
);

create table t_place(
id int(6) primary key auto_increment,
name varchar(40),
picpath mediumtext,
hotticket double(7,2),
dimticket double(7,2),
details varchar(300),
provinceid int(6) references t_province(id)
);

编码环节

环境准备

1、创建springboot项目

2、导入druid数据据源和文件上传依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>

3、编写配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 指定工程访问路径
server:
servlet:
context-path: /travelsAdmin

spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/travels?serverTimezone=Asia/Shanghai
username: root
password: 2824199842

mybatis:
type-aliases-package: com.zyz.bean
mapper-locations: classpath:com/zyz/mapper/*.xml
configuration:
map-underscore-to-camel-case: true

logging:
level:
com:
zyz:
dao: debug
service: info
controller: info

开始编码

用户模块

实现注册和登录功能

bean

1
2
3
4
5
6
7
8
9
package com.zyz.bean;

@Data
public class User {
private String id;
private String username;
private String password;
private String email;
}

dao

1
2
3
4
5
6
7
8
package com.zyz.dao;

@Mapper
@Repository
public interface UserDao {
void add(User user);
User queryUserByName(String name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zyz.dao.UserDao">

<insert id="add" parameterType="User" keyProperty="id" useGeneratedKeys="true">
insert into t_user values(#{id},#{username},#{password},#{email})
</insert>

<select id="queryUserByName" parameterType="String" resultType="User">
select id,username,password,email from t_user where username=#{username}
</select>
</mapper>

service

1
2
3
4
5
6
7
8
9
10
package com.zyz.service;

public interface UserService {

void register(User user);

User queryUserByName(String username);

User login(User user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.zyz.service.impl;

@Service
@Transactional
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;

@Override
public void register(User user) {
if (StringUtils.isEmpty(user.getUsername())) {
throw new RuntimeException("请输入用户名!");
}
if (StringUtils.isEmpty(user.getPassword())) {
throw new RuntimeException("请输入密码!");
}
if (StringUtils.isEmpty(user.getEmail())) {
throw new RuntimeException("请输入邮箱!");
}
// 判断用户名是否存在
User realUser = userDao.queryUserByName(user.getUsername());
if (realUser != null) {
throw new RuntimeException("该用户名已存在!");
}
userDao.add(user);
}

@Override
public User queryUserByName(String username) {
return userDao.queryUserByName(username);
}

@Override
public User login(User user) {
if (StringUtils.isEmpty(user.getUsername())) {
throw new RuntimeException("请输入用户名!");
}
if (StringUtils.isEmpty(user.getPassword())) {
throw new RuntimeException("请输入密码!");
}
User realuser = userDao.queryUserByName(user.getUsername());
if (realuser == null) {
throw new RuntimeException("用户名不存在或错误!");
}
if (!(user.getPassword().equals(realuser.getPassword()))) {
throw new RuntimeException("密码错误!");
}
return realuser;
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.zyz.controller;

@RestController
@RequestMapping("user")
public class UserController {

@Autowired
private UserService userService;

@PostMapping("login")
public Map<String,Object> login(@RequestBody User user,String code,HttpServletRequest request){
HashMap<String, Object> map = new HashMap<>();
try {
if (StringUtils.isEmpty(code)) {
throw new RuntimeException("请输入验证码!");
}
// 获取验证码
String realcode = (String) request.getServletContext().getAttribute("code");
if (!(realcode.equalsIgnoreCase(code))) {
throw new RuntimeException("验证码错误!");
}
User realUser = userService.login(user);
map.put("user",realUser);
map.put("status",true);
map.put("msg","提示:登录成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("status",false);
map.put("msg","提示:"+e.getMessage());
}
return map;
}

/**
* 用户登录
* @param user
* @param code
* @param request
* @return
*/
@PostMapping("register")
public Map<String, Object> register(@RequestBody User user, String code, HttpServletRequest request) {
HashMap<String, Object> map = new HashMap<>();
try {
// 判断验证码是否为空
if (StringUtils.isEmpty(code)) {
throw new RuntimeException("请输入验证码!");
}
// 获取验证码
String realcode = (String) request.getServletContext().getAttribute("code");
if (!(realcode.equalsIgnoreCase(code))) {
throw new RuntimeException("验证码错误!");
}
userService.register(user);
map.put("status",true);
map.put("msg","提示:注册成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("status", false);
map.put("msg", "提示:" + e.getMessage());
}
return map;
}
/**
* 展示验证码
* @param request
* @return
* @throws Exception
*/
@GetMapping("getImageCode")
public String getImageCode(HttpServletRequest request) throws Exception {
// 使用工具类生成验证码并输出
ByteArrayOutputStream os = new ByteArrayOutputStream();
String code = VerifyCodeUtils.generateVerifyCode(4);
VerifyCodeUtils.outputImage(100, 60, os, code);
// 保存至applicationContext域中
request.getServletContext().setAttribute("code", code);
// 将图片转换为base64格式,返回给前端
String s = Base64Utils.encodeToString(os.toByteArray());
return "data:image/png;base64," + s;
}
}

前端

使用v-model将表单中的数据与vue中data绑定

登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<script src="js/axios.min.js"></script>
<script src="js/vue.js"></script>
<script>
var app = new Vue({
el: "#wrap",
data:{
date: "",
url: "",
user:{},
code:"",
},
methods:{
getCode(){
axios.get("http://localhost:8080/travelsAdmin/user/getImageCode?time=" + Math.random())
.then(res => {
// console.log(res.data);
this.url = res.data;
});
},
getCodeImage(){ // 验证码点击事件
this.getCode();
},
login(){
console.log(this.user);
console.log(this.code);
axios.post("http://localhost:8080/travelsAdmin/user/login?code="+this.code,this.user)
.then(res=>{
//console.log();
if(res.data.status){
alert(res.data.msg+"点击确定跳转管理页面");
localStorage.setItem("user",JSON.stringify(res.data.user));
location.href="province/provincelist.html";
}else{
alert(res.data.msg+"点击确定重新登录");
}
})
}
},
created(){
// 显示验证码
this.getCode();
// 显示当前日期
var realDate = new Date();
this.date=realDate.getFullYear()+"/"+(realDate.getMonth()+1)+"/"+realDate.getDate();
},
})
</script>

注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<script>
var app = new Vue({
el: "#wrap",
data:{
date: "",
url: "",
user:{},
code:"",
},
methods:{
getCode(){
axios.get("http://localhost:8080/travelsAdmin/user/getImageCode?time=" + Math.random())
.then(res => {
// console.log(res.data);
this.url = res.data;
});
},
getCodeImage(){ // 验证码点击事件
this.getCode();
},
register(){ // 注册
// console.log(this.user);
// console.log(this.code);
// 发起异步请求
axios.post("http://localhost:8080/travelsAdmin/user/register?code="+this.code,this.user)
.then(res=>{
// 查看返回的数据
// console.log(res.data);
if(res.data.status){
if(window.confirm(res.data.msg+"点击确定跳转登录页面")){
location.href="login.html"
}else {
location.reload();
}
}else {
alert(res.data.msg);
}
})
}
},
created(){
// 显示验证码
this.getCode();
// 显示当前日期
var realDate = new Date();
this.date=realDate.getFullYear()+"/"+(realDate.getMonth()+1)+"/"+realDate.getDate();
},
})
</script>

在管理页面显示当前用户名:

1
2
3
4
5
6
7
8
created(){
var user = localStorage.getItem("user");
if(user==null){
location.href="http://localhost:8080/travelsAdmin/login.html"
}else{
this.user = JSON.parse(user);
}
},

用户退出:

1
<a href="javascript:;" @click="logout" style="float: right">安全退出</a>
1
2
3
4
5
6
methods:{
logout(){
localStorage.removeItem("user");
location.href="http://localhost:8080/travelsAdmin/login.html";
}
}

省份模块

展示所有省份

bean

1
2
3
4
5
6
7
8
9
package com.zyz.bean;

@Data
public class Province {
private String id;
private String name;
private String tags;
private Integer placecounts;
}

dao

抽取BaseDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
*
* @param <T> 类型
* @param <K> 条件参数的类型
*/
public interface BaseDao<T,K> {

void add(T t);

void delete(K k);

void update(T t);

T queryByName(K k);

T queryById(K k);

List<T> listByPage(Integer start,Integer rows);

Integer findTotal();

}

ProvinceDao继承BaseDao

1
2
3
4
5
6
7
package com.zyz.dao;

@Mapper
@Repository
public interface ProvinceDao extends BaseDao<Province,String>{

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zyz.dao.ProvinceDao">
<!-- 分页查询 -->
<select id="listByPage" resultType="Province">
select id,name,tags,placecounts from t_province
order by placecounts desc
limit #{start},#{rows}
</select>

<!-- 查询总记录数 -->
<select id="findTotal" resultType="Integer">
select count(*) from t_province
</select>

</mapper>

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.zyz.service;

public interface ProvinceService {

/**
* 分页查询
* @param page 当前页
* @param rows 每一页显示的记录数
* @return
*/
List<Province> listByPage(Integer page, Integer rows);

/**
* 查询总记录数
* @return
*/
Integer findTotal();

void add(Province province);

void delete(String id);

void update(Province province);

Province queryById(String id);

Province queryByName(String name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.zyz.service.impl;

@Service
@Transactional
public class ProvinceServiceImpl implements ProvinceService {

@Autowired
private ProvinceDao provinceDao;

@Override
public List<Province> listByPage(Integer page, Integer rows) {
int start = (page-1)*rows;
return provinceDao.listByPage(start,rows);
}

@Override
public Integer findTotal() {
return provinceDao.findTotal();
}

@Override
public void add(Province province) {

}

@Override
public void delete(String id) {

}

@Override
public void update(Province province) {

}

@Override
public Province queryByName(String name) {
return null;
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.zyz.controller;

@RestController
@RequestMapping("province")
public class ProvinceController {

@Autowired
private ProvinceService provinceService;

/**
* 查询所有省份
* @param page
* @param rows
* @return
*/
@GetMapping("listByPage")
public Map<String, Object> listByPage(Integer page, Integer rows) {
page = page == null ? 1 : page;
rows = rows == null ? 4 : rows;
HashMap<String, Object> map = new HashMap<>();
// 分页处理
List<Province> provinces = provinceService.listByPage(page, rows);
// 计算总页数
Integer total = provinceService.findTotal();
Integer totalPage = total % rows == 0 ? total / rows : (total / rows) + 1;
map.put("provinces", provinces);
map.put("total", total);
map.put("totalPage", totalPage);
map.put("page", page);
return map;
}
}

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<script>
var app = new Vue({
el: "#wrap",
data:{
date: "",
url: "",
user: {},
provinces: [],
page: 1,
rows: 0,
totalPage: 0,
total: 0,
},
methods:{
// 判断是否登录
isLogin(){
var user = localStorage.getItem("user");
if(user==null){
location.href="http://localhost:8080/travelsAdmin/login.html";
}else{
this.user = JSON.parse(user);
}
},
// 显示当前日期
showDate(){
var realDate = new Date();
this.date=realDate.getFullYear()+"/"+(realDate.getMonth()+1)+"/"+realDate.getDate();
},
// 退出
logout(){
localStorage.removeItem("user");
location.href="http://localhost:8080/travelsAdmin/login.html";
},
// 查询所有省份
listAll(page){
this.page = page;
axios.get("http://localhost:8080/travelsAdmin/province/listByPage?page="+this.page)
.then(res=>{
this.provinces = res.data.provinces;
this.page = res.data.page;
this.total = res.data.total;
this.totalPage = res.data.totalPage;
});
},
},
created(){
// 判断是否登录
this.isLogin();
// 显示当前日期
this.showDate();
// 查询所有省份
this.listAll(this.page);
}
})
</script>

遍历当前查询到的信息

1
2
3
4
5
6
7
8
<tbody>
<tr v-for="province in provinces" :key="province.id">
<td v-text="province.id"></td>
<td v-text="province.name"></td>
<td v-text="province.tags"></td>
<td v-text="province.placecounts"></td>
</tr>
</tbody>

分页控件

1
2
3
4
5
6
7
8
9
10
11
12
 <div id="pages">
<a href="javascript:;" v-if="page!=1" @click="listAll(page-1)" class="page">&lt;上一页</a>
<a href="javascript:;" v-if="page==1" class="page">&lt;上一页</a>

<span v-for="num in totalPage">
<a href="javascript:;" class="page" v-if="num!=page" @click="listAll(num)" v-text="num"></a>
<a href="javascript:;" class="page" v-if="num==page" v-text="num"></a>
</span>

<a href="javascript:;" v-if="page!=totalPage" @click="listAll(page+1)" class="page">下一页&gt;</a>
<a href="javascript:;" v-if="page==totalPage" class="page">下一页&gt;</a>
</div>

添加省份

dao

1
2
3
4
5
6
7
8
9
<!--  添加省份  -->
<insert id="add" parameterType="Province" useGeneratedKeys="true" keyProperty="id">
insert into t_province values(#{id},#{name},#{tags},#{placecounts})
</insert>

<!-- 根据名称查省份 -->
<select id="queryByName" parameterType="String" resultType="Province">
select id,name,tags,placecounts from t_province where name=#{name}
</select>

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void add(Province province) {
if(StringUtils.isEmpty(province.getName())){
throw new RuntimeException("请输入省份!");
}
if(StringUtils.isEmpty(province.getTags())){
throw new RuntimeException("请输入标签!");
}
if(StringUtils.isEmpty(province.getPlacecounts())){
throw new RuntimeException("请输入景点数量!");
}
Province realProvince = provinceDao.queryByName(province.getName());
if(!ObjectUtils.isEmpty(realProvince)){
throw new RuntimeException("该省份已存在!");
}
provinceDao.add(province);
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 添加省份
* @param province
* @return
*/
@PostMapping("add")
public Map<String,Object> add(@RequestBody Province province){
HashMap<String, Object> map = new HashMap<>();
try {
provinceService.add(province);
map.put("status",true);
map.put("msg","提示:添加成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("status",false);
map.put("msg","提示:"+e.getMessage());
}
return map;
}

前端

省份管理页:

1
<button type="button" @click="toAdd" >添加省份</button>
1
2
3
4
// 跳转添加页面
toAdd(){
location.href="addprovince.html";
}

添加页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 添加省份方法
add() {
axios.post("http://localhost:8080/travelsAdmin/province/add", this.province)
.then(res => {
if (res.data.status) {
if (window.confirm(res.data.msg + "点击确定跳转管理页面")) {
location.href = "provincelist.html";
} else {
location.reload();
}
} else {
alert(res.data.msg);
}
})
}

删除省份

dao

1
2
3
4
<!--  删除省份  -->
<delete id="delete" parameterType="String">
delete from t_province where id = #{id}
</delete>

serivce

1
2
3
4
@Override
public void delete(String id) {
provinceDao.delete(id);
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 删除省份
* @param id
* @return
*/
@GetMapping("delete")
public Map<String,Object> delete(String id){
HashMap<String, Object> map = new HashMap<>();
try {
provinceService.delete(id);
map.put("status",true);
map.put("msg","提示:删除成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("status",false);
map.put("msg","提示:删除失败");
}
return map;
}

前端

1
<a href="javascript:;" @click="deletePro(province.id)">删除省份</a>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 删除省份
deletePro(id) {
if (window.confirm("确定删除吗?")) {
axios.get("http://localhost:8080/travelsAdmin/province/delete?id="+id)
.then(res => {
if (res.data.status) {
alert(res.data.msg);
location.reload();
} else {
alert(res.data.msg);
}
})
}
}

修改省份

dao

1
2
3
4
5
6
7
8
9
10
<!--  根据id查询省份  -->
<select id="queryById" parameterType="String" resultType="Province">
select id,name,tags,placecounts from t_province where id = #{id}
</select>

<!-- 修改省份 -->
<update id="update" parameterType="Province">
update t_province set name=#{name},tags=#{tags},placecounts=#{placecounts}
where id=#{id}
</update>

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void update(Province province) {
if(StringUtils.isEmpty(province.getName())){
throw new RuntimeException("请输入省份!");
}
if(StringUtils.isEmpty(province.getTags())){
throw new RuntimeException("请输入标签!");
}
if(StringUtils.isEmpty(province.getPlacecounts())){
throw new RuntimeException("请输入景点数量!");
}
provinceDao.update(province);
}

@Override
public Province queryById(String id) {
return provinceDao.queryById(id);
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 修改省份
* @param province
* @return
*/
@PostMapping("update")
public Map<String,Object> update(@RequestBody Province province){
HashMap<String, Object> map = new HashMap<>();
try {
provinceService.update(province);
map.put("status",true);
map.put("msg","提示:修改成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("status",false);
map.put("msg","提示:"+e.getMessage());
}
return map;
}

/**
* 获取省份
* @param id
* @return
*/
@GetMapping("getProvince")
public Province getProvince(String id){
Province province = provinceService.queryById(id);
return province;
}

前端

管理页面:

1
<a href="javascript:;" @click="toUpdate(province.id)">修改省份</a>
1
2
3
4
// 跳转修改页面
toUpdate(id) {
location.href = "updateprovince.html?id=" + id;
}

修改页面:

1
<button type="button" @click="update">修 改</button>&emsp;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<script>
var app = new Vue({
el: "#wrap",
data: {
date: "",
province: {},
},
methods: {
// 显示当前日期方法
showDate() {
var realDate = new Date();
this.date = realDate.getFullYear() + "/" + (realDate.getMonth() + 1) + "/" + realDate.getDate();
},
// 根据id查询当前省份
getProvince(){
// 获取id
var start = location.href.lastIndexOf("=");
var id = location.href.substring(start+1);
axios.get("http://localhost:8080/travelsAdmin/province/getProvince?id="+id)
.then(res=>{
// console.log(res.data);
this.province = res.data;
})
},
// 修改省份方法
update() {
axios.post("http://localhost:8080/travelsAdmin/province/update", this.province)
.then(res => {
if (res.data.status) {
alert(res.data.msg);
location.href = "provincelist.html";
} else {
alert(res.data.msg);
}
})
}
},
created() {
// 显示当前日期
this.showDate();
// 查询当前省份的信息
this.getProvince();
},
})
</script>

景点模块

根据省份展示该省份的所有景点

创建实体类

1
2
3
4
5
6
7
8
9
10
11
12
package com.zyz.bean;

@Data
public class Place {
private String id;
private String name;
private String picpath;
private Double hotticket;
private Double dimticket;// 淡季门票
private String details;
private String provinceid;
}

dao层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.zyz.dao;

@Mapper
@Repository
public interface PlaceDao extends BaseDao<Place,String>{

/**
* 根据省份id进行分页查询
* @param start
* @param rows
* @param provinceId
* @return
*/
List<Place> listByProvinceId(Integer start,Integer rows,String provinceId);

/**
* 根据省份查询景点总数
* @param provinceId
* @return
*/
Integer findTotalByProvinceId(String provinceId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zyz.dao.PlaceDao">

<!-- 分页查询当前省份的所有景点信息并排序 -->
<select id="listByProvinceId" resultType="Place">
select id,name,picpath,hotticket,dimticket,details,provinceid
from t_place where provinceid = #{provinceId}
order by hotticket
limit #{start},#{rows}
</select>

<!-- 查询当前省份的景点的个数 -->
<select id="findTotalByProvinceId" parameterType="String" resultType="Integer">
select count(*) from t_place where provinceid=#{provinceId}
</select>

</mapper>

serivce层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.zyz.service;

public interface PlaceService {

/**
* 根据省份id进行分页查询
* @param page
* @param rows
* @param provinceId
* @return
*/
List<Place> listByProvinceId(Integer page, Integer rows, String provinceId);

/**
* 根据省份查询景点总数
* @param provinceId
* @return
*/
Integer findTotalByProvinceId(String provinceId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.zyz.service.impl;

@Service
@Transactional
public class PlaceServiceImpl implements PlaceService {

@Autowired
private PlaceDao placeDao;

@Autowired
private ProvinceDao provinceDao;

@Override
public List<Place> listByProvinceId(Integer page, Integer rows, String provinceId) {
int start = (page-1)*rows;
return placeDao.listByProvinceId(start,rows,provinceId);
}

@Override
public Integer findTotalByProvinceId(String provinceId) {
return placeDao.findTotalByProvinceId(provinceId);
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.zyz.controller;

@RestController
@RequestMapping("place")
public class PlaceController {

@Autowired
private PlaceService placeService;
/**
* 展示所有景点
* @param page
* @param rows
* @param provinceId
* @return
*/
@GetMapping("listAll")
public Map<String, Object> listAll(Integer page, Integer rows, String provinceId) {
page = page == null ? 1 : page;
rows = rows == null ? 3 : rows;
// 总记录数
Integer total = placeService.findTotalByProvinceId(provinceId);
// 总页数
Integer totalPage = total % rows == 0 ? total / rows : (total / rows) + 1;
// 景点信息
List<Place> places = placeService.listByProvinceId(page, rows, provinceId);
HashMap<String, Object> map = new HashMap<>();
map.put("total", total);
map.put("totalPage", totalPage);
map.put("places", places);
map.put("page", page);
return map;
}
}

增加景点

dao层

继承了BaseDao中的增删改方法,以及添加所需要的判断景点是否已经存在 的queryByName()方法,修改所需要的回显当前景点信息的queryById()方法,因此只需要编写对应的mapper文件就行

1
2
3
4
5
6
7
8
9
10
<!--添加景点-->
<insert id="add" parameterType="Place" useGeneratedKeys="true" keyProperty="id">
insert into t_place
values(#{id},#{name},#{picpath},#{hotticket},#{dimticket},#{details},#{provinceid})
</insert>
<!-- 根据名称查询景点 -->
<select id="queryByName" parameterType="String" resultType="Place">
select id,name,picpath,hotticket,dimticket,details,provinceid
from t_place where name = #{name}
</select>

service层,添加add()方法并实现

1
void add(Place place);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void add(Place place) {
if(StringUtils.isEmpty(place.getName())){
throw new RuntimeException("请输入景点名!");
}
if(StringUtils.isEmpty(place.getHotticket())){
throw new RuntimeException("请输入旺季门票价格!");
}
if(StringUtils.isEmpty(place.getDimticket())){
throw new RuntimeException("请输入淡季门票价格!");
}
if(StringUtils.isEmpty(place.getProvinceid())){
throw new RuntimeException("请选择省份!");
}
Place place1= placeDao.queryByName(place.getName());
if(!ObjectUtils.isEmpty(place1)){
throw new RuntimeException("该景点已存在!");
}
// 更新此景点省份的景点数量
Province province = provinceDao.queryById(place.getProvinceid());
province.setPlacecounts(province.getPlacecounts()+1);
provinceDao.update(province);
placeDao.add(place);
}

controller层 编写处理添加请求的方法

配置文件上传路径:

1
2
3
# 文件上传路径
upload:
dir: D:\img
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 注入文件上传路径
@Value("${upload.dir}")
private String uploadPath;

/**
* 添加景点
* @param place
* @param file
* @return
*/
@PostMapping("add")
public Map<String, Object> add(Place place, MultipartFile file) {
Map<String, Object> map = new HashMap<>();
try {
if (!ObjectUtils.isEmpty(file)) {
// 修改文件名
String filename = UUID.randomUUID().toString()+"."
+ FilenameUtils.getExtension(file.getOriginalFilename());
// 将文件转换为base64格式存储到数据库中
place.setPicpath(Base64Utils.encodeToString(file.getBytes()));
placeService.add(place);
// 文件上传
file.transferTo(new File(uploadPath,filename));
map.put("status",true);
map.put("msg","提示:添加成功!");
}else{
throw new RuntimeException("请上传图片!");
}
} catch (Exception e) {
e.printStackTrace();
map.put("status",false);
map.put("msg", "提示"+e.getMessage());
}
return map;
}

修改前端页面

携带省份id跳转到添加页面,便于返回

1
2
3
4
5
toAdd(){
var start = location.href.indexOf("=");
var provinceid = location.href.substring(start+1);
location.href = "addviewspot.html?provinceid="+provinceid;
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<script>
var app = new Vue({
el: "#wrap",
data: {
date: "",
provinces: [],
place: {
name: "",
picpath: "",
hotticket: "",
dimticket: "",
details: "",
provinceid: "",
myfile: ""
},
},
methods: {
// 判断是否登录
isLogin() {
var user = localStorage.getItem("user");
if (user == null) {
alert("请登录!");
location.href = "http://localhost:8080/travelsAdmin/login.html";
}
},
// 显示当前日期
showDate() {
var realDate = new Date();
this.date = realDate.getFullYear() + "/" + (realDate.getMonth() + 1) + "/" + realDate.getDate();
},
// 查询省份
listAllProvince() {
axios.get("http://localhost:8080/travelsAdmin/province/listByPage?rows=35")
.then(res => {
//console.log(res.data.provinces);
this.provinces = res.data.provinces;
});
},
// 添加景点
add() {
// 构造表单
var formData = new FormData();
formData.append("name", this.place.name);
formData.append("hotticket", this.place.hotticket);
formData.append("dimticket", this.place.dimticket);
formData.append("provinceid", this.place.provinceid);
formData.append("details", this.place.details);
formData.append("file", this.$refs.myfile.files[0]);
console.log(formData);
axios({
method: "post",
url: "http://localhost:8080/travelsAdmin/place/add",
data: formData,
headers: {'Content-Type': 'multipart/form-data'}
}).then(res => {
if (res.data.status) {
if (window.confirm(res.data.msg + "点击确定跳转景点管理页面")) {
location.href = "viewspotlist.html?provinceid=" + this.place.provinceid;
} else {
location.reload();
}
} else {
alert(res.data.msg + "点击确定重新添加")
}
})
},
back() {
var start = location.href.indexOf("=");
var provinceid = location.href.substring(start + 1);
location.href = "viewspotlist.html?provinceid=" + provinceid;
}
},
created() {
// 判断是否登录
this.isLogin();
// 显示当前日期
this.showDate();
// 查询所有省份
this.listAllProvince();
},
})
</script>

删除景点

dao

1
2
3
4
5
6
7
8
9
10
<!-- 删除景点   -->
<delete id="delete" parameterType="String">
delete from t_place where id = #{id}
</delete>
<!-- 根据id查询景点信息 -->
<select id="queryById" resultType="Place" parameterType="String">
select id,name,picpath,hotticket,dimticket,details,provinceid
from t_place
where id =#{id}
</select>

service

1
void delete(String id);
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void delete(String id) {
// 该景点的省份景点数-1
// 先查询该景点的所有信息
Place place = placeDao.queryById(id);
// 查询该景点所在省份的信息
Province province = provinceDao.queryById(place.getProvinceid());
// 更新该省份的景点数
province.setPlacecounts(province.getPlacecounts()-1);
provinceDao.update(province);
// 删除景点
placeDao.delete(id);
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 删除景点
* @param id
* @return
*/
@GetMapping("delete")
public Map<String,Object> delete(String id){
HashMap<String, Object> map = new HashMap<>();
try {
placeService.delete(id);
map.put("status",true);
map.put("msg","提示:删除成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("status",false);
map.put("msg","提示:"+e.getMessage());
}
return map;
}

前端

1
2
3
4
5
6
7
8
9
10
11
deletePlace(id) {
if (window.confirm("确定删除此景点吗?")) {
axios.get("http://localhost:8080/travelsAdmin/place/delete?id=" + id)
.then(res => {
if (res.data.status) {
alert("删除成功!");
location.reload();
}
})
}
},

修改景点信息

service

1
2
3
Place queryById(String id);

void update(Place place);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public Place queryById(String id) {
return placeDao.queryById(id);
}

@Override
public void update(Place place) {
if(StringUtils.isEmpty(place.getName())){
throw new RuntimeException("请输入景点名!");
}
if(StringUtils.isEmpty(place.getHotticket())){
throw new RuntimeException("请输入旺季门票价格!");
}
if(StringUtils.isEmpty(place.getDimticket())){
throw new RuntimeException("请输入淡季门票价格!");
}
placeDao.update(place);
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 根据id查询景点
* @param id
* @return
*/
@GetMapping("queryById")
public Place queryById(String id ){
return placeService.queryById(id);
}

/**
* 修改景点信息
* @param place
* @param photo
* @return
*/
@PostMapping("update")
public Map<String,Object> update(Place place,MultipartFile photo){
HashMap<String, Object> map = new HashMap<>();
try {
if(!ObjectUtils.isEmpty(photo)){
String filename = UUID.randomUUID().toString()+"."
+FilenameUtils.getExtension(photo.getOriginalFilename());
// 更新picpath
place.setPicpath(Base64Utils.encodeToString(photo.getBytes()));
// 上传到指定路径
photo.transferTo(new File(uploadPath,filename));
}
// 保存至数据库
placeService.update(place);
map.put("status",true);
map.put("msg","提示:修改成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("status",false);
map.put("msg","提示:"+ e.getMessage());
}
return map;
}

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<script>
var app = new Vue({
el: "#wrap",
data: {
date: "",
id: "",
place: {
name:"",
picpath:"",
hotticket:"",
dimticket:"",
details:"",
provinceid:"",
myfile:""
},
provinces: [],
src:"",
},
methods: {
// 判断是否登录
isLogin() {
var user = localStorage.getItem("user");
if (user == null) {
alert("请登录!");
location.href = "http://localhost:8080/travelsAdmin/login.html";
}
},
// 显示当前日期
showDate() {
var realDate = new Date();
this.date = realDate.getFullYear() + "/" + (realDate.getMonth() + 1) + "/" + realDate.getDate();
},
showView(){
// 获取id
var start = location.href.indexOf("=");
var id = location.href.substring(start+1);
axios.get("http://localhost:8080/travelsAdmin/place/queryById?id="+id)
.then(res=>{
// console.log(res.data);
this.place = res.data;
// 显示图片
this.src = "data:image/png;base64,"+this.place.picpath;
this.id = id;
})
},
queryProvinces(){
axios.get("http://localhost:8080/travelsAdmin/province/listByPage?rows=35")
.then(res=>{
this.provinces = res.data.provinces;
})
},
back(){
location.href="viewspotlist.html?provinceid="+this.place.provinceid;
},
updatePlace(){
var formData = new FormData();
formData.append("id",this.place.id);
formData.append("name",this.place.name);
formData.append("picpath",this.place.picpath);
formData.append("photo",this.$refs.myfile.files[0]);
formData.append("hotticket",this.place.hotticket);
formData.append("dimticket",this.place.dimticket);
formData.append("provinceid",this.place.provinceid)
formData.append("details",this.place.details)
axios({
method: "post",
url: "http://localhost:8080/travelsAdmin/place/update",
data: formData,
headers:{'content-type':'multipart/formdata'}
}).then(res=>{
if(res.data.status){
alert(res.data.msg+"点击确定跳转景点管理页面");
location.href = "viewspotlist.html?provinceid="+this.place.provinceid;
}else{
alert(res.data.msg);
}
})
}
},
created() {
// 判断是否登录
this.isLogin();
// 显示当前日期
this.showDate();
// 显示当前待修改景点的信息
this.queryProvinces();
this.showView();
},
})
</script>

图片上传与显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
function imgfileChange() {
var img_show = document.getElementById("img-show");
var imgfile = document.getElementById("imgfile");

var freader = new FileReader();
freader.readAsDataURL(imgfile.files[0]);
freader.onload = function (e) {
img_show.src = e.target.result;
};
}
</script>

<label>
<div class="label-text">印象图片:</div>
<div style="text-align: center;padding-left: 36%">
<img :src="src" alt="" id="img-show">
<input type="file" id="imgfile" style="display: none" onchange="imgfileChange()"
ref="myfile">
</div>
</label>

省份选择框:

1
2
3
4
5
6
7
<label>
<div class="label-text">所属省份:</div>
<select name="ofprovince" v-model="place.provinceid" style="height: 25px;">
<option disabled>请选择省份</option>
<option v-for="pro in provinces" v-text="pro.name" :value="pro.id"></option>
</select>
</label>

使用Redis作为Mybatis的缓存

1、配置Redis地址和端口

1
2
3
4
spring:
redis:
host: 127.0.0.1
port: 6379

2、实现mybatis中的Cache接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.zyz.cache;

public class RedisCache implements Cache {

private String id;

public RedisCache( String id){
this.id = id;
}

// 获取RedisTemplate
public RedisTemplate getRedisTemplate(){
RedisTemplate template =(RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
return template;
}

@Override
public String getId() {
return this.id;
}

// 将数据存入Redis
@Override
public void putObject(Object key, Object value) {
getRedisTemplate().opsForHash().put(id,key.toString(),value);
}

// 从Redis中获取数据
@Override
public Object getObject(Object key) {
return getRedisTemplate().opsForHash().get(id,key.toString());
}

// 删除指定缓存信息
@Override
public Object removeObject(Object key) {
return getRedisTemplate().opsForHash().delete(id,key.toString());
}

// 清除缓存
@Override
public void clear() {
getRedisTemplate().delete(id);
}

// 获取数据大小
@Override
public int getSize() {
return getRedisTemplate().opsForValue().size(id).intValue();
}
}

工具类ApplicationContextUtils获取工厂实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.zyz.utils;

@Component
public class ApplicationContextUtils implements ApplicationContextAware {

// 当前工厂
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

// 获取工厂中的实现类
public static Object getBean(String name){
return applicationContext.getBean(name);
}
}

3、在mapper文件中添加自定义的Redis缓存

1
<cache type="com.zyz.cache.RedisCache"></cache>

4、存储在Redis中的实体类必须实现Serializable接口

主要界面

用户模块

省份模块

景点模块

项目总结

做了什么?

  • 管理员的注册和登录
  • 省份信息的增删改查
  • 景点信息的增删改查

学到了什么?

  • 在前后端分离中分页查询功能的实现
  • 使用mysql中的meduimtext数据类型存储图片,图片需要先进行base64编码然后存进数据库
  • vue中选择框的数据双向绑定

遇到的问题?怎么解决的?

  • 在完成添加景点功能时,在浏览器端调试总是出现undefined的错误,仔细与之前的添加功能相比,此添加功能涉及到了文件的上传,需要构造表单,然后在表单中添加数据
<img src="project-02/12.png" style="zoom:80%;" />
  • 在修改景点信息时,提交一直显示图片未上传,仔细读出现不难发现,如果在修改景点信息时沿用添加景点时的那种方式,
    所以一直显示图片未上传,只要修改一下判断逻辑就可以了,在判断没有修改图片时直接更新,修改图片后进行重命名,文件上传然后更新

  • 景点省份为空