Spring Boot 学习笔记 - 钢钢更新

背景介绍

该文档是在慕课网实战课程《2019版 微服务时代Spring Boot企业微信点餐系统》基础上总结而成,旨在记录Spring Boot一些相关知识,文章中涉及的代码都经过验证,可以直接使用。
该文档作为个人参考资料,会长期更新。

慕课网课程地址:2019版 微服务时代Spring Boot企业微信点餐系统

数据库设计

微信点餐数据库 - SQL.md

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
-- 类目
create table `product_category` (
`category_id` int not null auto_increment,
`category_name` varchar(64) not null comment '类目名字',
`category_type` int not null comment '类目编号',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`category_id`)
);

-- 商品
create table `product_info` (
`product_id` varchar(32) not null,
`product_name` varchar(64) not null comment '商品名称',
`product_price` decimal(8,2) not null comment '单价',
`product_stock` int not null comment '库存',
`product_description` varchar(64) comment '描述',
`product_icon` varchar(512) comment '小图',
`product_status` tinyint(3) DEFAULT '0' COMMENT '商品状态,0正常1下架',
`category_type` int not null comment '类目编号',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`product_id`)
);

-- 订单
create table `order_master` (
`order_id` varchar(32) not null,
`buyer_name` varchar(32) not null comment '买家名字',
`buyer_phone` varchar(32) not null comment '买家电话',
`buyer_address` varchar(128) not null comment '买家地址',
`buyer_openid` varchar(64) not null comment '买家微信openid',
`order_amount` decimal(8,2) not null comment '订单总金额',
`order_status` tinyint(3) not null default '0' comment '订单状态, 默认为新下单',
`pay_status` tinyint(3) not null default '0' comment '支付状态, 默认未支付',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`order_id`),
key `idx_buyer_openid` (`buyer_openid`)
);

-- 订单商品
create table `order_detail` (
`detail_id` varchar(32) not null,
`order_id` varchar(32) not null,
`product_id` varchar(32) not null,
`product_name` varchar(64) not null comment '商品名称',
`product_price` decimal(8,2) not null comment '当前价格,单位分',
`product_quantity` int not null comment '数量',
`product_icon` varchar(512) comment '小图',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`detail_id`),
key `idx_order_id` (`order_id`)
);

-- 卖家(登录后台使用, 卖家登录之后可能直接采用微信扫码登录,不使用账号密码)
create table `seller_info` (
`id` varchar(32) not null,
`username` varchar(32) not null,
`password` varchar(32) not null,
`openid` varchar(64) not null comment '微信openid',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`id`)
) comment '卖家信息表';

API接口

商品列表

1
GET /sell/buyer/product/list

参数

1

返回

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
{
"code": 0,
"msg": "成功",
"data": [
{
"name": "热榜",
"type": 1,
"foods": [
{
"id": "123456",
"name": "皮蛋粥",
"price": 1.2,
"description": "好吃的皮蛋粥",
"icon": "http://xxx.com",
}
]
},
{
"name": "好吃的",
"type": 2,
"foods": [
{
"id": "123457",
"name": "慕斯蛋糕",
"price": 10.9,
"description": "美味爽口",
"icon": "http://xxx.com",
}
]
}
]
}

创建订单

1
POST /sell/buyer/order/create

参数

1
2
3
4
5
6
7
8
name: "张三"
phone: "18868822111"
address: "慕课网总部"
openid: "ew3euwhd7sjw9diwkq" //用户的微信openid
items: [{
productId: "1423113435324",
productQuantity: 2 //购买数量
}]

返回

1
2
3
4
5
6
7
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "147283992738221"
}
}

订单列表

1
GET /sell/buyer/order/list

参数

1
2
3
openid: 18eu2jwk2kse3r42e2e
page: 0 //从第0页开始
size: 10

返回

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
{
"code": 0,
"msg": "成功",
"data": [
{
"orderId": "161873371171128075",
"buyerName": "张三",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 0,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490171219,
"updateTime": 1490171219,
"orderDetailList": null
},
{
"orderId": "161873371171128076",
"buyerName": "张三",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 0,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490171219,
"updateTime": 1490171219,
"orderDetailList": null
}]
}

查询订单详情

1
GET /sell/buyer/order/detail

参数

1
2
openid: 18eu2jwk2kse3r42e2e
orderId: 161899085773669363

返回

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
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "161899085773669363",
"buyerName": "李四",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 18,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490177352,
"updateTime": 1490177352,
"orderDetailList": [
{
"detailId": "161899085974995851",
"orderId": "161899085773669363",
"productId": "157875196362360019",
"productName": "招牌奶茶",
"productPrice": 9,
"productQuantity": 2,
"productIcon": "http://xxx.com",
"productImage": "http://xxx.com"
}
]
}
}

取消订单

1
POST /sell/buyer/order/cancel

参数

1
2
openid: 18eu2jwk2kse3r42e2e
orderId: 161899085773669363

返回

1
2
3
4
5
{
"code": 0,
"msg": "成功",
"data": null
}

获取openid

1
重定向到 /sell/wechat/authorize

参数

1
returnUrl: http://xxx.com/abc  //【必填】

返回

1
http://xxx.com/abc?openid=oZxSYw5ldcxv6H0EU67GgSXOUrVg

支付订单

1
重定向 /sell/pay/create

参数

1
2
orderId: 161899085773669363
returnUrl: http://xxx.com/abc/order/161899085773669363

返回

1
http://xxx.com/abc/order/161899085773669363

IntelliJ Idea 项目结构


POM依赖

pom.xml

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.imooc</groupId>
<artifactId>sell</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>sell</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>

<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>2.7.0</version>
</dependency>

<dependency>
<groupId>cn.springboot</groupId>
<artifactId>best-pay-sdk</artifactId>
<version>1.1.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>


<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-cache</artifactId>-->
<!--</dependency>-->
</dependencies>

<build>
<finalName>sell</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>


</project>

应用配置

全局配置文件

application.yml

1
2
3
spring:
profiles:
active: dev

开发配置文件

application-dev.yml

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
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false
jpa:
show-sql: true
jackson:
default-property-inclusion: non_null
redis:
host: 192.168.30.113
port: 6379
server:
context-path: /sell
#logging:
# pattern:
# console: "%d - %msg%n"
## path: /var/log/tomcat/
# file: /var/log/tomcat/sell.log
# level:
# com.imooc.LoggerTest: debug

wechat:
mpAppId: wxd898fcb01713c658
mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx
openAppId: wx6ad144e54af67d87
openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx
mchId: 1483469312
mchKey: 06C56A89949D617xxxxxxxxxxx
keyPath: /var/weixin_cert/h5.p12
notifyUrl: http://sell.natapp4.cc/sell/pay/notify
templateId:
orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ

projectUrl:
wechatMpAuthorize: http://sell.natapp4.cc
wechatOpenAuthorize: http://sell.natapp4.cc
sell: http://sell.natapp4.cc
logging:
level:
com.imooc.dataobject.mapper: trace
mybatis:
mapper-locations: classpath:mapper/*.xml

生产配置文件

application-prod.yml

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
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false
jackson:
default-property-inclusion: non_null
redis:
host: 192.168.30.113
port: 6379
server:
context-path: /sell
#logging:
# pattern:
# console: "%d - %msg%n"
## path: /var/log/tomcat/
# file: /var/log/tomcat/sell.log
# level:
# com.imooc.LoggerTest: debug

wechat:
mpAppId: wxd898fcb01713c658
mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx
openAppId: wx6ad144e54af67d87
openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx
mchId: 1483469312
mchKey: 06C56A89949D617xxxxxxxxxxx
keyPath: /var/weixin_cert/h5.p12
notifyUrl: http://sell.natapp4.cc/sell/pay/notify
templateId:
orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ

projectUrl:
wechatMpAuthorize: http://sell.natapp4.cc
wechatOpenAuthorize: http://sell.natapp4.cc
sell: http://sell.natapp4.cc
logging:
level:
com.imooc.dataobject.mapper: trace
mybatis:
mapper-locations: classpath:mapper/*.xml

配置文件类

BlogProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mindex.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@ConfigurationProperties(prefix = "com.mindex.blog")
@Component
public class BlogProperties {
private String name;
private String desc;
}

引用配置信息

BlogPropertiesTest

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
package com.mindex.config;

import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class BlogPropertiesTest {

@Autowired
private BlogProperties blogProperties;

@Test
public void getProperties() throws Exception {
Assert.assertEquals("轮子王", blogProperties.getName());
Assert.assertEquals("用行动改变世界", blogProperties.getDesc());
}

}

自定义配置文件

WechatAccountConfig

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

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;

@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {

private String mpAppId;
private String mpAppSecret;
private String openAppId;
private String openAppSecret;
private String mchId;
private String mchKey;
private String keyPath;
private String notifyUrl;
private Map<String, String> templateId;
}

引用自定义的配置文件

WechatMpConfig

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
package com.imooc.config;

import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class WechatMpConfig {

@Autowired
private WechatAccountConfig accountConfig;

@Bean
public WxMpService wxMpService() {
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
return wxMpService;
}

@Bean
public WxMpConfigStorage wxMpConfigStorage() {
WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage();
wxMpConfigStorage.setAppId(accountConfig.getMpAppId());
wxMpConfigStorage.setSecret(accountConfig.getMpAppSecret());
return wxMpConfigStorage;
}
}

日志处理

SLF4j

Logback

logback-spring.xml

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
<?xml version="1.0" encoding="UTF-8" ?>

<configuration>

<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%d - %msg%n
</pattern>
</layout>
</appender>

<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>/Users/kwang/imooc/sell/log/info.%d.log</fileNamePattern>
</rollingPolicy>
</appender>


<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>/Users/kwang/imooc/sell/log/error.%d.log</fileNamePattern>
</rollingPolicy>
</appender>

<root level="info">
<appender-ref ref="consoleLog" />
<appender-ref ref="fileInfoLog" />
<appender-ref ref="fileErrorLog" />
</root>

</configuration>

Swagger2 文档工具

引入POM依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>

创建Swagger配置类

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
package com.mindex.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class Swagger2Configuration {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// 自行修改为自己的包路径
.apis(RequestHandlerSelectors.basePackage("com.mindex.controller"))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("api文档")
.description("Restful 风格接口")
.version("1.0")
.build();
}
}

在controller中引入Swagger注解

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
package com.mindex.controller;

import com.mindex.entities.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@RestController
@RequestMapping(value = "/users")
@Api(value = "/users", tags = "测试接口模块")
public class UserController {

//创建线程安全的Map
static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());

@ApiOperation(value = "获取用户列表", notes = "")
@RequestMapping(value = "/", method = RequestMethod.GET)
public List<User> getUserList() {
// 处理"/users/"的GET请求,用来获取用户列表
// 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
List<User> userList = new ArrayList<User>(users.values());
return userList;
}

@ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
@ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
@RequestMapping(value = "/", method = RequestMethod.POST)
public String postUser(@ModelAttribute User user) {
// 处理"/users/"的POST请求,用来创建User
// 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数
users.put(user.getId(), user);
return "success";
}


@ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable Long id) {
// 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
// url中的id可通过@PathVariable绑定到函数的参数中
return users.get(id);
}

@ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"),
@ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
})
@RequestMapping(value = "/{id}", method = RequestMethod.POST)
public String putUser(@PathVariable Long id, @ModelAttribute User user) {
// 处理"/users/{id}"的PUT请求,用来更新User信息
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}

@ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String deleteUser(@PathVariable Long id) {
// 处理"/users/{id}"的DELETE请求,用来删除User
users.remove(id);
return "success";
}
}

启动tomcat查看文档

http://localhost:8080/swagger-ui.html


IDEA插件

JRebel插件

引入POM依赖

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>1.5.1.RELEASE</version>
<scope>provided</scope>
</dependency>

配置Application.java

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
@ComponentScan(basePackages = "com.mindex")
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

配置maven project选项

选中Lifecycle-clean及compile

安装JRebel插件

配置JRebel插件

在IDEA里新建一个部署配置项。

运行测试

Lombok插件

好处:安装了Lombok插件和pom引用依赖后,可以简化代码,例如:无需再写get/set/toString方法,打印日志时直接使用log关键字等。

安装Lombok插件

安装步骤在这里

引用pom依赖

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

好处1:只要使用@Data@Getter@Setter@ToString等注解,无需再写繁琐的get/set/toString方法,Lombok会在编译时自动加入代码。

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.imooc.dataobject;

import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;

@Entity
@DynamicUpdate
@Data
public class ProductCategory {

@Id
@GeneratedValue
private Integer categoryId;

private String categoryName;
private Integer categoryType;
private Date createTime;
private Date updateTime;

public ProductCategory(String categoryName, Integer categoryType) {
this.categoryName = categoryName;
this.categoryType = categoryType;
}

public ProductCategory() {
}
}

好处2:输出日志时,可以直接使用log关键字输出,支持参数引用。

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
package com.imooc;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = LoggerTest.class)
@Slf4j
public class LoggerTest {

// 无需再写LoggerFactory
// private final Logger logger = LoggerFactory.getLogger(LoggerTest.class);

@Test
public void test1() {
String name = "imooc";
String password = "12345";
log.debug("debug...");
log.info("name: {}, password: {}", name, password);
log.error("error...");
log.warn("warning...");
}
}

项目运行类(主入口)

SellApplication

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

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SellApplication {

public static void main(String[] args) {
SpringApplication.run(SellApplication.class, args);
}
}

Enums枚举类

ResultEnum

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.imooc.enums;

import lombok.Getter;

@Getter
public enum ResultEnum {
PARAM_ERROR(1, "参数错误"),
PRODUCT_NOT_EXIST(10, "商品不存在"),
PRODUCT_STOCK_ERROR(11, "商品库存不足"),
ORDER_NOT_EXIST(12, "订单不存在"),
ORDERDETAIL_NOT_EXIST(13, "订单详情不存在"),
ORDER_STATUS_ERROR(14, "订单状态错误"),
ORDER_UPDATE_ERROR(15, "订单更新失败"),
ORDER_DETAIL_EMPTY(16, "订单详情为空"),
ORDER_PAY_STATUS_ERROR(17, "订单支付状态不正确"),
CART_IS_EMPTY(18, "购物车为空"),
ORDER_OWNER_ERROR(19, "该订单不属于当前用户"),
WECHAT_MP_ERROR(20, "微信公众号授权错误"),
ORDER_FINISH_SUCCESS(21, "订单完结成功"),
ORDER_CANCEL_SUCCESS(22, "订单取消成功"),
PRODUCT_STATUS_ERROR(23, "商品状态错误"),
;

private Integer code;

private String msg;

ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

}

CodeEnum

1
2
3
4
5
6
package com.imooc.enums;

public interface CodeEnum {

Integer getCode();
}

OrderStatusEnum

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

import lombok.Getter;

@Getter
public enum OrderStatusEnum implements CodeEnum {
NEW(0, "新订单"),
FINISH(1, "已完结"),
CANCEL(2, "已取消"),
;

private Integer code;

private String msg;

OrderStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

}

PayStatusEnum

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

import lombok.Getter;

@Getter
public enum PayStatusEnum implements CodeEnum {
WAIT(0, "等待支付"),
SUCCESS(1, "已支付");

private Integer code;

private String msg;

PayStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}

ProductStatusEnum

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

import lombok.Getter;

@Getter
public enum ProductStatusEnum implements CodeEnum {
UP(0, "上架"),
DOWN(1, "下架");

private Integer code;

private String message;

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

Util工具类

可以把常用的方法放在util包里,比如拼接VO视图、生成唯一编码等;

构造结果VO视图 - ResultVOUtil

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
package com.imooc.utils;

import com.imooc.VO.ResultVO;

public class ResultVOUtil {

public static ResultVO success(Object object) {
ResultVO resultVO = new ResultVO();
resultVO.setData(object);
resultVO.setCode(0);
resultVO.setMsg("成功");
return resultVO;
}

public static ResultVO success() {
return success(null);
}

public static ResultVO error(Integer code, String msg) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(code);
resultVO.setMsg(msg);
return resultVO;
}
}

生成随机id - KeyUtil

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

import java.util.Random;

public class KeyUtil {
/**
* 生成唯一的主键
* 格式: 时间+随机数
*
* @return
*/
public static synchronized String genUniqueKey() {
Random random = new Random();

Integer number = random.nextInt(900000) + 100000;
return System.currentTimeMillis() + String.valueOf(number);
}
}

object转json - JsonUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.imooc.utils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JsonUtil {

public static String toJson(Object object) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setPrettyPrinting();
Gson gson = gsonBuilder.create();
return gson.toJson(object);
}
}

Cookie工具类 - CookieUtil

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
package com.imooc.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

public class CookieUtil {

/**
* 设置
* @param response
* @param name
* @param value
* @param maxAge
*/
public static void set(HttpServletResponse response,
String name,
String value,
int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}

/**
* 获取cookie
* @param request
* @param name
* @return
*/
public static Cookie get(HttpServletRequest request,
String name) {
Map<String, Cookie> cookieMap = readCookieMap(request);
if (cookieMap.containsKey(name)) {
return cookieMap.get(name);
}else {
return null;
}
}

/**
* 将cookie封装成Map
* @param request
* @return
*/
private static Map<String, Cookie> readCookieMap(HttpServletRequest request) {
Map<String, Cookie> cookieMap = new HashMap<>();
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie: cookies) {
cookieMap.put(cookie.getName(), cookie);
}
}
return cookieMap;
}
}

比较金额(double类型)是否相等 - MathUtil

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

public class MathUtil {

private static final Double MONEY_RANGE = 0.01;

/**
* 比较2个金额是否相等
* @param d1
* @param d2
* @return
*/
public static Boolean equals(Double d1, Double d2) {
Double result = Math.abs(d1 - d2);
if (result < MONEY_RANGE) {
return true;
}else {
return false;
}
}
}

EnumUtil

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

import com.imooc.enums.CodeEnum;

public class EnumUtil {

public static <T extends CodeEnum> T getByCode(Integer code, Class<T> enumClass) {
for (T each: enumClass.getEnumConstants()) {
if (code.equals(each.getCode())) {
return each;
}
}
return null;
}
}

Date2LongSerializer

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

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;
import java.util.Date;

public class Date2LongSerializer extends JsonSerializer<Date> {

@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(date.getTime() / 1000);
}
}

Data Object层(Entity)

主要用来映射数据库表及字段关系。

ProductCategory

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
package com.imooc.dataobject;

import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Proxy;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;

@Entity
@DynamicUpdate
@Data
@Proxy(lazy = false)
public class ProductCategory {

/**
* 类目id.
*/
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
private Integer categoryId;

/**
* 类目名称.
*/
private String categoryName;

/**
* 类目编号.
*/
private Integer categoryType;

private Date createTime;

private Date updateTime;

public ProductCategory() {
}

public ProductCategory(String categoryName, Integer categoryType) {
this.categoryName = categoryName;
this.categoryType = categoryType;
}
}

ProductInfo

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
package com.imooc.dataobject;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.imooc.enums.ProductStatusEnum;
import com.imooc.utils.EnumUtil;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.SelectBeforeUpdate;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;

@Entity
@Data
@DynamicUpdate
public class ProductInfo {

@Id
private String productId;

/** 名字. */
private String productName;

/** 单价. */
private BigDecimal productPrice;

/** 库存. */
private Integer productStock;

/** 描述. */
private String productDescription;

/** 小图. */
private String productIcon;

/** 状态, 0正常1下架. */
private Integer productStatus;

/** 类目编号. */
private Integer categoryType;

private Date createTime;

private Date updateTime;

@JsonIgnore
public ProductStatusEnum getProductStatusEnum() {
return EnumUtil.getByCode(productStatus, ProductStatusEnum.class);
}

}

OrderMaster

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
package com.imooc.dataobject;

import com.imooc.enums.OrderStatusEnum;
import com.imooc.enums.PayStatusEnum;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;

@Entity
@Data
@DynamicUpdate
//@Proxy(lazy = false)
public class OrderMaster {

/** 订单id. */
@Id
private String orderId;

/** 买家名字. */
private String buyerName;

/** 买家手机号. */
private String buyerPhone;

/** 买家地址. */
private String buyerAddress;

/** 买家微信Openid. */
private String buyerOpenid;

/** 订单总金额. */
private BigDecimal orderAmount;

/** 订单状态, 默认为0新下单. */
private Integer orderStatus = OrderStatusEnum.NEW.getCode();

/** 支付状态, 默认为0未支付. */
private Integer payStatus = PayStatusEnum.WAIT.getCode();

/** 创建时间. */
private Date createTime;

/** 更新时间. */
private Date updateTime;

}

OrderDetail

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
package com.imooc.dataobject;

import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.math.BigDecimal;

@Entity
@Data
@DynamicUpdate
public class OrderDetail {

@Id
private String detailId;

/** 订单id. */
private String orderId;

/** 商品id. */
private String productId;

/** 商品名称. */
private String productName;

/** 商品单价. */
private BigDecimal productPrice;

/** 商品数量. */
private Integer productQuantity;

/** 商品小图. */
private String productIcon;


}

Repository层 (DAO) - JPA

JpaRepository对数据库常用操作进行了封装,通过继承JpaRepository可以快速实现数据库操作。
JpaRepository第一个参数是data object,第二个是该data object的主键。

Repository定义

ProductCategoryRepository

1
2
3
4
5
6
7
8
9
10
11
package com.imooc.repository;

import com.imooc.dataobject.ProductCategory;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface ProductCategoryRepository extends JpaRepository<ProductCategory, Integer> {

List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);
}

repository单元测试

ProductCategoryRepositoryTest

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
package com.imooc.repository;

import com.imooc.dataobject.ProductCategory;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductCategoryRepositoryTest {

@Autowired
private ProductCategoryRepository repository;

@Test
public void findOneTest() {
ProductCategory productCategory = repository.getOne(1);
System.out.println(productCategory.toString());
}

@Test
public void saveTest() {
// ProductCategory productCategory = repository.getOne(2);
// productCategory.setCategoryType(11);

ProductCategory productCategory = new ProductCategory("女生最爱", 3);
// productCategory.setCategoryId(2);
// productCategory.setCategoryName("男生最爱");
// productCategory.setCategoryType(3);
ProductCategory result = repository.save(productCategory);
Assert.assertNotNull(result);
}

@Test
public void findByCategoryTypeInTest() {
List<Integer> list = Arrays.asList(1, 2, 3, 4);
List<ProductCategory> result = repository.findByCategoryTypeIn(list);
Assert.assertNotEquals(0, result);
}

}

ProductInfoRepository

1
2
3
4
5
6
7
8
9
10
11
package com.imooc.repository;

import com.imooc.dataobject.ProductInfo;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface ProductInfoRepository extends JpaRepository<ProductInfo, String> {

List<ProductInfo> findByProductStatus(Integer productStatus);
}

OrderMasterRepository

1
2
3
4
5
6
7
8
9
10
11
package com.imooc.repository;

import com.imooc.dataobject.OrderMaster;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderMasterRepository extends JpaRepository<OrderMaster, String> {

Page<OrderMaster> findByBuyerOpenid(String buyerOpenid, Pageable pageable);
}

OrderDetailRepository

1
2
3
4
5
6
7
8
9
10
11
package com.imooc.repository;

import com.imooc.dataobject.OrderDetail;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface OrderDetailRepository extends JpaRepository<OrderDetail, String> {

List<OrderDetail> findByOrderId(String orderId);
}

Service层

CategoryService

CategoryService

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

import com.imooc.dataobject.ProductCategory;

import java.util.List;

public interface CategoryService {

ProductCategory findOne(Integer categoryId);

List<ProductCategory> findAll();

List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);

ProductCategory save(ProductCategory productCategory);
}

需要使用 @Service 注解

CategoryServiceImpl

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
package com.imooc.service.impl;

import com.imooc.dataobject.ProductCategory;
import com.imooc.repository.ProductCategoryRepository;
import com.imooc.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CategoryServiceImpl implements CategoryService {

@Autowired
private ProductCategoryRepository repository;

@Override
public ProductCategory findOne(Integer categoryId) {
return repository.findOne(categoryId);
}

@Override
public List<ProductCategory> findAll() {
return repository.findAll();
}

@Override
public List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) {
return repository.findByCategoryTypeIn(categoryTypeList);
}

@Override
public ProductCategory save(ProductCategory productCategory) {
return repository.save(productCategory);
}
}

CategoryServiceImplTest

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
package com.imooc.service.impl;

import com.imooc.dataobject.ProductCategory;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CategoryServiceImplTest {

@Autowired
private CategoryServiceImpl categoryService;

@Test
public void findOne() {
ProductCategory productCategory = categoryService.findOne(1);
Assert.assertEquals(new Integer(1), productCategory.getCategoryId());
}

@Test
public void findAll() {
List<ProductCategory> productCategoryList = categoryService.findAll();
Assert.assertNotEquals(0, productCategoryList.size());
}

@Test
public void findByCategoryTypeIn() {
List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(Arrays.asList(1, 2, 3, 4));
Assert.assertNotEquals(0, productCategoryList.size());

}

@Test
public void save() {
ProductCategory productCategory = new ProductCategory("男生专享", 10);
ProductCategory result = categoryService.save(productCategory);
Assert.assertNotNull(result);
}
}

ProductService

ProductService

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.imooc.service;

import com.imooc.dataobject.ProductInfo;
import com.imooc.dto.CartDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;

public interface ProductService {

ProductInfo findOne(String productId);

List<ProductInfo> findUpAll();

Page<ProductInfo> findAll(Pageable pageable);

ProductInfo save(ProductInfo productInfo);

//加库存
void increaseStock(List<CartDTO> cartDTOList);

//减库存
void reduceStock(List<CartDTO> cartDTOList);

//下架
ProductInfo offSale(String productId);

//上架
ProductInfo onSale(String productId);

}

ProductServiceImpl

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.imooc.service.impl;

import com.imooc.dataobject.ProductInfo;
import com.imooc.dto.CartDTO;
import com.imooc.enums.ProductStatusEnum;
import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import com.imooc.repository.ProductInfoRepository;
import com.imooc.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;

@Service
public class ProductServiceImpl implements ProductService {

@Autowired
private ProductInfoRepository repository;

@Override
public ProductInfo findOne(String productId) {
return repository.getOne(productId);
}

@Override
public List<ProductInfo> findUpAll() {
return repository.findByProductStatus(ProductStatusEnum.UP.getCode());
}

@Override
public Page<ProductInfo> findAll(Pageable pageable) {
return repository.findAll(pageable);
}

@Override
@Transactional
public ProductInfo save(ProductInfo productInfo) {
return repository.save(productInfo);
}

@Override
public void increaseStock(List<CartDTO> cartDTOList) {
for (CartDTO cartDTO : cartDTOList) {
ProductInfo productInfo = repository.findById(cartDTO.getProductId()).get();
if (productInfo == null) {
throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
}

Integer result = productInfo.getProductStock() + cartDTO.getProductQuantity();
productInfo.setProductStock(result);
repository.save(productInfo);
}
}

@Override
@Transactional
public void reduceStock(List<CartDTO> cartDTOList) {
for (CartDTO cartDTO : cartDTOList) {
ProductInfo productInfo = repository.findById(cartDTO.getProductId()).get();
if (productInfo == null) {
throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
}

Integer result = productInfo.getProductStock() - cartDTO.getProductQuantity();
if (result < 0) {
throw new SellException(ResultEnum.PRODUCT_STOCK_ERROR);
}

productInfo.setProductStock(result);
repository.save(productInfo);
}
}

@Override
public ProductInfo offSale(String productId) {
ProductInfo productInfo = repository.findById(productId).get();
if (productInfo == null) {
throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
}

if (productInfo.getProductStatusEnum() == ProductStatusEnum.DOWN) {
throw new SellException(ResultEnum.PRODUCT_STATUS_ERROR);
}

productInfo.setProductStatus(ProductStatusEnum.DOWN.getCode());
return repository.save(productInfo);
}

@Override
public ProductInfo onSale(String productId) {
ProductInfo productInfo = repository.findById(productId).get();
if (productInfo == null) {
throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
}

if (productInfo.getProductStatusEnum() == ProductStatusEnum.UP) {
throw new SellException(ResultEnum.PRODUCT_STATUS_ERROR);
}

productInfo.setProductStatus(ProductStatusEnum.UP.getCode());
return repository.save(productInfo);
}
}

ProductServiceImplTest

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
package com.imooc.service.impl;

import com.imooc.dataobject.ProductInfo;
import com.imooc.enums.ProductStatusEnum;
import com.imooc.service.ProductService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.List;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductServiceImplTest {

@Autowired
private ProductServiceImpl productService;

@Test
public void findOne() {
ProductInfo productInfo = productService.findOne("123456");
Assert.assertEquals("123456", productInfo.getProductId());
}

@Test
public void findUpAll() {
List<ProductInfo> productInfoList = productService.findUpAll();
Assert.assertNotEquals(0, productInfoList.size());
}

@Test
public void findAll() {
PageRequest request = new PageRequest(0, 2);
Page<ProductInfo> productInfoPage = productService.findAll(request);
Assert.assertNotEquals(0, productInfoPage.getTotalElements());
}

@Test
public void save() {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductId("123457");
productInfo.setProductName("皮皮虾");
productInfo.setProductDescription("好喝");
productInfo.setProductPrice(new BigDecimal(99.8));
productInfo.setProductStock(10);
productInfo.setProductIcon("http://yyy.jpg");
productInfo.setProductStatus(1);
productInfo.setCategoryType(2);

ProductInfo result = productService.save(productInfo);
Assert.assertNotNull(result);
}

@Test
public void offSale() {
ProductInfo result = productService.offSale("123456");
Assert.assertEquals(result.getProductStatusEnum(), ProductStatusEnum.DOWN);
}

@Test
public void onSale() {
ProductInfo result = productService.onSale("123456");
Assert.assertEquals(result.getProductStatusEnum(), ProductStatusEnum.UP);
}
}

OrderService

OrderService

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.imooc.service;

import com.imooc.dto.OrderDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface OrderService {

/**
* 创建订单
*/
OrderDTO create(OrderDTO orderDTO);

/**
* 查询单个订单
*/
OrderDTO findOne(String orderId);

/**
* 查询订单列表
*/
Page<OrderDTO> findList(String buyerOpenid, Pageable pageable);

/**
* 取消订单
*/
OrderDTO cancel(OrderDTO orderDTO);

/**
* 完结订单
*/
OrderDTO finish(OrderDTO orderDTO);

/**
* 支付订单
*/
OrderDTO paid(OrderDTO orderDTO);

Page<OrderDTO> findList(Pageable pageable);
}

OrderServiceImpl

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package com.imooc.service.impl;

import com.imooc.converter.OrderMaster2OrderDTOConverter;
import com.imooc.dataobject.OrderDetail;
import com.imooc.dataobject.OrderMaster;
import com.imooc.dataobject.ProductInfo;
import com.imooc.dto.CartDTO;
import com.imooc.dto.OrderDTO;
import com.imooc.enums.OrderStatusEnum;
import com.imooc.enums.PayStatusEnum;
import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import com.imooc.repository.OrderDetailRepository;
import com.imooc.repository.OrderMasterRepository;
import com.imooc.service.OrderService;
import com.imooc.service.ProductService;
import com.imooc.utils.KeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.transaction.Transactional;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

@Autowired
private ProductService productService;

@Autowired
private OrderDetailRepository orderDetailRepository;

@Autowired
private OrderMasterRepository orderMasterRepository;


@Override
@Transactional
public OrderDTO create(OrderDTO orderDTO) {

String orderId = KeyUtil.genUniqueKey();
BigDecimal orderAmount = new BigDecimal(BigInteger.ZERO);

// 1. 查询商品(数量、价格)
for (OrderDetail orderDetail :
orderDTO.getOrderDetailList()) {
ProductInfo productInfo = productService.findOne(orderDetail.getProductId());
if (productInfo == null) {
throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
}


// 2. 计算订单总价
orderAmount = productInfo.getProductPrice()
.multiply(new BigDecimal(orderDetail.getProductQuantity()))
.add(orderAmount);


// 3. 保存订单详情
BeanUtils.copyProperties(productInfo, orderDetail);
orderDetail.setDetailId(KeyUtil.genUniqueKey());
orderDetail.setOrderId(orderId);

orderDetailRepository.save(orderDetail);

}


// 4. 保存订单头
OrderMaster orderMaster = new OrderMaster();
orderDTO.setOrderId(orderId);
BeanUtils.copyProperties(orderDTO, orderMaster);
orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
orderMaster.setOrderAmount(orderAmount);

orderMasterRepository.save(orderMaster);

// 5. 扣库存
List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream()
.map(e -> new CartDTO(e.getProductId(), e.getProductQuantity()))
.collect(Collectors.toList());

productService.reduceStock(cartDTOList);

return orderDTO;
}

@Override
public OrderDTO findOne(String orderId) {
OrderMaster orderMaster = orderMasterRepository.findById(orderId).orElseThrow(() -> new SellException(ResultEnum.ORDER_NOT_EXIST));


List<OrderDetail> orderDetailList = orderDetailRepository.findByOrderId(orderId);
if (CollectionUtils.isEmpty(orderDetailList)) {
throw new SellException(ResultEnum.ORDERDETAIL_NOT_EXIST);
}

OrderDTO orderDTO = new OrderDTO();
BeanUtils.copyProperties(orderMaster, orderDTO);
orderDTO.setOrderDetailList(orderDetailList);

return orderDTO;
}

@Override
public Page<OrderDTO> findList(String buyerOpenid, Pageable pageable) {
Page<OrderMaster> orderMasterPage = orderMasterRepository.findByBuyerOpenid(buyerOpenid, pageable);
List<OrderDTO> orderDTOList = OrderMaster2OrderDTOConverter.convert(orderMasterPage.getContent());
Page<OrderDTO> orderDTOPage = new PageImpl<OrderDTO>(orderDTOList, pageable, orderMasterPage.getTotalElements());
return orderDTOPage;
}

@Override
public Page<OrderDTO> findList(Pageable pageable) {
Page<OrderMaster> orderMasterPage = orderMasterRepository.findAll(pageable);
List<OrderDTO> orderDTOList = OrderMaster2OrderDTOConverter.convert(orderMasterPage.getContent());
Page<OrderDTO> orderDTOPage = new PageImpl<OrderDTO>(orderDTOList, pageable, orderMasterPage.getTotalElements());
return orderDTOPage;
}

@Override
@Transactional
public OrderDTO cancel(OrderDTO orderDTO) {
// 确定订单状态为可取消
if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) {
log.error("【订单状态错误】order status = {}", orderDTO.getOrderStatus());
throw new SellException(ResultEnum.ORDER_STATUS_ERROR);
}

// 取消订单
OrderMaster orderMaster = new OrderMaster();
orderDTO.setOrderStatus(OrderStatusEnum.CANCEL.getCode());

// 拷贝的位置很重要!!
BeanUtils.copyProperties(orderDTO, orderMaster);
OrderMaster updateResult = orderMasterRepository.save(orderMaster);
if (updateResult == null) {
log.error("【订单状态错误】order status = {}", orderDTO.getOrderStatus());
throw new SellException(ResultEnum.ORDER_UPDATE_ERROR);
}

// 回退商品库存
if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
throw new SellException(ResultEnum.ORDER_DETAIL_EMPTY);
}

List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream().map(
e -> new CartDTO(e.getProductId(), e.getProductQuantity())
).collect(Collectors.toList());
productService.increaseStock(cartDTOList);

// 如果用户已付款,需要退款
if (orderDTO.getPayStatus().equals(PayStatusEnum.SUCCESS)) {
//TODO 退款逻辑
}

return orderDTO;
}

@Override
@Transactional
public OrderDTO finish(OrderDTO orderDTO) {
// 判断订单状态
if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) {
log.error("【完结订单】订单状态不正确,orderDTO = {}", orderDTO);
throw new SellException(ResultEnum.ORDER_STATUS_ERROR);
}

// 完结订单
OrderMaster orderMaster = new OrderMaster();
orderDTO.setOrderStatus(OrderStatusEnum.FINISH.getCode());
BeanUtils.copyProperties(orderDTO, orderMaster);
OrderMaster updateResult = orderMasterRepository.save(orderMaster);

if (updateResult == null) {
log.error("【完结订单】订单更新失败,orderDTO = {}", orderDTO);
throw new SellException(ResultEnum.ORDER_UPDATE_ERROR);
}

return orderDTO;
}

@Override
@Transactional
public OrderDTO paid(OrderDTO orderDTO) {
// 判断订单状态
if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) {
log.error("【支付订单】订单状态不正确,orderDTO = {}", orderDTO);
throw new SellException(ResultEnum.ORDER_STATUS_ERROR);
}

// 判断支付状态
if (!orderDTO.getPayStatus().equals(PayStatusEnum.WAIT.getCode())) {
log.error("【支付订单】订单支付状态不正确,orderDTO = {}", orderDTO);
throw new SellException(ResultEnum.ORDER_PAY_STATUS_ERROR);
}

// 更新支付状态
OrderMaster orderMaster = new OrderMaster();
orderDTO.setPayStatus(PayStatusEnum.SUCCESS.getCode());
BeanUtils.copyProperties(orderDTO, orderMaster);
OrderMaster updateResult = orderMasterRepository.save(orderMaster);

if (updateResult == null) {
log.error("【支付订单】订单支付状态更新失败,orderDTO = {}", orderDTO);
throw new SellException(ResultEnum.ORDER_UPDATE_ERROR);
}

return orderDTO;
}


}

OrderServiceImplTest

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
91
92
93
94
95
96
97
98
99
100
101
package com.imooc.service.impl;

import com.imooc.dataobject.OrderDetail;
import com.imooc.dto.OrderDTO;
import com.imooc.enums.OrderStatusEnum;
import com.imooc.enums.PayStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class OrderServiceImplTest {

@Autowired
private OrderServiceImpl orderService;

private final String BUYER_OPENID = "110110";

private final String ORDER_ID = "1559635196265398489";

@Test
public void create() {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setBuyerName("kenny");
orderDTO.setBuyerAddress("上海");
orderDTO.setBuyerPhone("18612341234");
orderDTO.setBuyerOpenid(BUYER_OPENID);

List<OrderDetail> orderDetailList = new ArrayList<>();

OrderDetail o1 = new OrderDetail();
o1.setProductId("12345678");
o1.setProductQuantity(1);
orderDetailList.add(o1);

OrderDetail o2 = new OrderDetail();
o2.setProductId("123456");
o2.setProductQuantity(2);
orderDetailList.add(o2);

orderDTO.setOrderDetailList(orderDetailList);

OrderDTO result = orderService.create(orderDTO);
log.info("【创建订单】result={}", result);

Assert.assertNotNull(result);
}

@Test
public void findOne() {
OrderDTO result = orderService.findOne(ORDER_ID);
log.info("【订单信息】result={}", result);
Assert.assertEquals(ORDER_ID, result.getOrderId());
}

@Test
public void findList() {
PageRequest request = new PageRequest(0, 2);
Page<OrderDTO> orderDTOPage = orderService.findList(BUYER_OPENID, request);
Assert.assertNotEquals(0, orderDTOPage);
}

@Test
public void findAll() {
PageRequest request = new PageRequest(0, 2);
Page<OrderDTO> orderDTOPage = orderService.findList(request);
Assert.assertNotEquals(0, orderDTOPage);
}

@Test
public void cancel() {
OrderDTO orderDTO = orderService.findOne(ORDER_ID);
OrderDTO result = orderService.cancel(orderDTO);
Assert.assertEquals(OrderStatusEnum.CANCEL.getCode(), result.getOrderStatus());
}

@Test
public void finish() {
OrderDTO orderDTO = orderService.findOne(ORDER_ID);
OrderDTO result = orderService.finish(orderDTO);
Assert.assertEquals(OrderStatusEnum.FINISH.getCode(), result.getOrderStatus());
}

@Test
public void paid() {
OrderDTO orderDTO = orderService.findOne(ORDER_ID);
OrderDTO result = orderService.paid(orderDTO);
Assert.assertEquals(PayStatusEnum.SUCCESS.getCode(), result.getPayStatus());
}
}

BuyerService

BuyerService

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

import com.imooc.dto.OrderDTO;

public interface BuyerService {

// 查询一个订单
OrderDTO findOrderOne(String openid, String orderId);

// 取消一个订单
OrderDTO cancelOrder(String openid, String orderId);
}

BuyerServiceImpl

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
package com.imooc.service.impl;

import com.imooc.dto.OrderDTO;
import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import com.imooc.service.BuyerService;
import com.imooc.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class BuyerServiceImpl implements BuyerService {

@Autowired
private OrderService orderService;

@Override
public OrderDTO findOrderOne(String openid, String orderId) {
return checkOrderOwner(openid, orderId);
}

@Override
public OrderDTO cancelOrder(String openid, String orderId) {
OrderDTO orderDTO = checkOrderOwner(openid, orderId);
if (orderDTO == null) {
log.error("【检查订单】查询不到该订单。openid={}, orderId={}", openid, orderId);
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
orderService.cancel(orderDTO);
return orderDTO;
}

public OrderDTO checkOrderOwner(String openid, String orderId) {
OrderDTO orderDTO = orderService.findOne(orderId);
if (orderDTO == null) {
return null;
}

if (!orderDTO.getBuyerOpenid().equalsIgnoreCase(openid)) {
log.error("【检查订单】该订单不属于当前用户。openid={}, orderDTO={}", openid, orderDTO);
throw new SellException(ResultEnum.ORDER_OWNER_ERROR);
}

return orderDTO;
}
}

Controller层

@RestController 注解,直接返回json格式;
@RequestMapping(“buyer/product”) 注解声明服务的url前缀;

BuyerProductController

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
package com.imooc.controller;

import com.imooc.VO.ProductInfoVO;
import com.imooc.VO.ProductVO;
import com.imooc.VO.ResultVO;
import com.imooc.dataobject.ProductCategory;
import com.imooc.dataobject.ProductInfo;
import com.imooc.service.CategoryService;
import com.imooc.service.ProductService;
import com.imooc.utils.ResultVOUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;


@RestController
@RequestMapping("/buyer/product")
public class BuyerProductController {

@Autowired
private ProductService productService;

@Autowired
private CategoryService categoryService;

@GetMapping("/list")
public ResultVO list() {

//1. 查询所有上架商品
List<ProductInfo> productInfoList = productService.findUpAll();

// List<Integer> categoryTypeList = new ArrayList<>();
// for (ProductInfo productInfo :
// productInfoList) {
// categoryTypeList.add(productInfo.getCategoryType());
// }

List<Integer> categoryTypeList = productInfoList.stream()
.map(e -> e.getCategoryType())
.collect(Collectors.toList());

//2. 查询品类
List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList);


//3. 拼装数据
List<ProductVO> productVOList = new ArrayList<>();

for (ProductCategory productCategory :
productCategoryList) {
ProductVO productVO = new ProductVO();
productVO.setCategoryName(productCategory.getCategoryName());
productVO.setCategoryType(productCategory.getCategoryType());


List<ProductInfoVO> productInfoVOList = new ArrayList<>();
for (ProductInfo productInfo :
productInfoList) {
if (productInfo.getCategoryType().equals(productCategory.getCategoryType())) {
ProductInfoVO productInfoVO = new ProductInfoVO();
BeanUtils.copyProperties(productInfo, productInfoVO);

productInfoVOList.add(productInfoVO);
}
}

productVO.setProductInfoVOList(productInfoVOList);
productVOList.add(productVO);
}


return ResultVOUtil.success(productVOList);
}
}

BuyerOrderController

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
91
92
93
94
95
package com.imooc.controller;

import com.imooc.VO.ResultVO;
import com.imooc.converter.OrderForm2OrderDTOConverter;
import com.imooc.dto.OrderDTO;
import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import com.imooc.form.OrderForm;
import com.imooc.service.BuyerService;
import com.imooc.service.OrderService;
import com.imooc.utils.ResultVOUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@RestController
@RequestMapping("/buyer/order")
@Slf4j
public class BuyerOrderController {

@Autowired
private OrderService orderService;

@Autowired
private BuyerService buyerService;

// 创建订单
@PostMapping("/create")
public ResultVO<Map<String, String>> create(@Valid OrderForm orderForm, BindingResult bindingResult) {

if (bindingResult.hasErrors()) {
log.error("【创建订单】参数不正确, orderForm = {}", orderForm);
throw new SellException(ResultEnum.PARAM_ERROR.getCode(), bindingResult.getFieldError().getDefaultMessage());
}

OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
log.error("【创建订单】购物车不能为空, orderDTO={}", orderDTO);
throw new SellException(ResultEnum.CART_IS_EMPTY);
}

OrderDTO createResult = orderService.create(orderDTO);

Map<String, String> map = new HashMap<>();
map.put("orderId", createResult.getOrderId());

return ResultVOUtil.success(map);
}

// 订单列表
@GetMapping("/list")
public ResultVO<List<OrderDTO>> list(@RequestParam(value = "openid") String openid,
@RequestParam(value = "page", defaultValue = "0") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size) {
if (StringUtils.isEmpty(openid)) {
log.error("【查询订单列表】openid不能为空");
throw new SellException(ResultEnum.PARAM_ERROR);
}

PageRequest request = new PageRequest(page, size);
Page<OrderDTO> orderDTO = orderService.findList(openid, request);

return ResultVOUtil.success(orderDTO.getContent());

}


// 订单详情
@GetMapping("/detail")
public ResultVO<List<OrderDTO>> detail(@RequestParam("openid") String openid,
@RequestParam("orderId") String orderId) {
OrderDTO orderDTO = buyerService.findOrderOne(openid, orderId);
return ResultVOUtil.success(orderDTO);
}

// 取消订单
@PostMapping("/cancel")
public ResultVO<List<OrderDTO>> cancel(@RequestParam("openid") String openid,
@RequestParam("orderId") String orderId) {
OrderDTO orderDTO = buyerService.cancelOrder(openid, orderId);
return ResultVOUtil.success();
}

}

SellerCategoryController

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
91
92
93
94
package com.imooc.controller;

import com.imooc.dataobject.ProductCategory;
import com.imooc.exception.SellException;
import com.imooc.form.CategoryForm;
import com.imooc.service.CategoryService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import javax.validation.Valid;
import java.util.List;
import java.util.Map;

/**
* 卖家商品类目
*/
@Controller
@RequestMapping("/seller/category")
public class SellerCategoryController {

@Autowired
private CategoryService categoryService;

/**
* 商品类目列表
*
* @param map
* @return
*/
@GetMapping("/list")
public ModelAndView list(Map<String, Object> map) {
List<ProductCategory> categoryList = categoryService.findAll();
map.put("categoryList", categoryList);
return new ModelAndView("category/list", map);
}

/**
* 商品类目新增/更新
*
* @param categoryId
* @param map
* @return
*/
@GetMapping("index")
private ModelAndView index(@RequestParam(value = "categoryId", required = false) Integer categoryId,
Map<String, Object> map) {
if (categoryId != null) {
ProductCategory productCategory = categoryService.findOne(categoryId);
map.put("category", productCategory);
}
return new ModelAndView("category/index", map);
}

/**
* 保存/更新
* @param form
* @param bindingResult
* @param map
* @return
*/
@PostMapping("/save")
public ModelAndView save(@Valid CategoryForm form,
BindingResult bindingResult,
Map<String, Object> map) {
if (bindingResult.hasErrors()) {
map.put("msg", bindingResult.getFieldError().getDefaultMessage());
map.put("url", "/sell/seller/category/index");
return new ModelAndView("common/error", map);
}

ProductCategory productCategory = new ProductCategory();
try {
if (form.getCategoryId() != null) {
productCategory = categoryService.findOne(form.getCategoryId());
}
BeanUtils.copyProperties(form, productCategory);
categoryService.save(productCategory);
} catch (SellException e) {
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/category/index");
return new ModelAndView("common/error", map);
}

map.put("url", "/sell/seller/category/list");
return new ModelAndView("common/success", map);
}
}

SellerOrderController

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.imooc.controller;

import com.imooc.dto.OrderDTO;
import com.imooc.enums.OrderStatusEnum;
import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import com.imooc.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

/**
* 卖家端订单
*/
@Controller
@RequestMapping("/seller/order")
@Slf4j
public class SellerOrderController {

@Autowired
private OrderService orderService;

/**
* 卖家端订单列表
*
* @param page 第几页, 从1页开始
* @param size 一页有多少条数据
* @return
*/
@GetMapping("/list")
public ModelAndView list(@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
Map<String, Object> map) {
PageRequest request = new PageRequest(page - 1, size);
Page<OrderDTO> orderDTOPage = orderService.findList(request);
map.put("orderDTOPage", orderDTOPage);
map.put("currentPage", page);
map.put("size", size);
return new ModelAndView("order/list", map);
}

/**
* 卖家端订单取消
*
* @param orderId
* @param map
* @return
*/
@GetMapping("/cancel")
public ModelAndView cancel(@RequestParam(value = "orderId") String orderId,
Map<String, Object> map) {
try {
OrderDTO orderDTO = orderService.findOne(orderId);
orderService.cancel(orderDTO);
} catch (SellException e) {
log.error("【取消订单】订单取消异常, {}", e);
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/order/list");

return new ModelAndView("common/error", map);
}

map.put("msg", ResultEnum.ORDER_CANCEL_SUCCESS.getMsg());
map.put("url", "/sell/seller/order/list");
return new ModelAndView("common/success", map);
}

/**
* 卖家端订单详情
* @param orderId
* @param map
* @return
*/
@GetMapping("/detail")
public ModelAndView detail(@RequestParam(value = "orderId") String orderId,
Map<String, Object> map) {
OrderDTO orderDTO = new OrderDTO();
try {
orderDTO = orderService.findOne(orderId);
} catch (SellException e) {
log.error("【卖家端订单详情】获取订单详情发生异常:{}", e);
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/order/list");
return new ModelAndView("common/error", map);
}

map.put("orderDTO", orderDTO);
return new ModelAndView("order/detail", map);
}

/**
* 卖家端订单完结
* @param orderId
* @param map
* @return
*/
@GetMapping("/finish")
public ModelAndView finish(@RequestParam(value = "orderId") String orderId,
Map<String, Object> map) {
OrderDTO orderDTO = new OrderDTO();
try {
orderDTO = orderService.findOne(orderId);
orderService.finish(orderDTO);
} catch (SellException e) {
log.error("【卖家端完结订单异常】{}", e);
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/order/list");
return new ModelAndView("common/error", map);
}

map.put("msg", ResultEnum.ORDER_FINISH_SUCCESS.getMsg());
map.put("url", "/sell/seller/order/list");
return new ModelAndView("common/success", map);
}
}

SellerProductController

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.imooc.controller;

import com.imooc.dataobject.ProductCategory;
import com.imooc.dataobject.ProductInfo;
import com.imooc.enums.ProductStatusEnum;
import com.imooc.exception.SellException;
import com.imooc.form.ProductForm;
import com.imooc.service.CategoryService;
import com.imooc.service.ProductService;
import com.imooc.utils.KeyUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import javax.validation.Valid;
import java.util.List;
import java.util.Map;

/**
* 卖家端商品列表
*/
@Controller
@RequestMapping("/seller/product")
public class SellerProductController {

@Autowired
private ProductService productService;

@Autowired
private CategoryService categoryService;

/**
* 商品列表
*
* @param page
* @param size
* @param map
* @return
*/
@GetMapping("/list")
public ModelAndView list(@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
Map<String, Object> map) {
PageRequest request = new PageRequest(page - 1, size);
Page<ProductInfo> productInfoPage = productService.findAll(request);
map.put("productInfoPage", productInfoPage);
map.put("currentPage", page);
map.put("size", size);
return new ModelAndView("product/list", map);
}

@RequestMapping("/on_sale")
public ModelAndView onSale(@RequestParam(value = "productId") String productId,
Map<String, Object> map) {
try {
productService.onSale(productId);
} catch (SellException e) {
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/error", map);
}

map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/success", map);
}

@RequestMapping("/off_sale")
public ModelAndView offSale(@RequestParam(value = "productId") String productId,
Map<String, Object> map) {
try {
productService.offSale(productId);
} catch (SellException e) {
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/error", map);
}

map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/success", map);
}

@RequestMapping("/index")
public ModelAndView index(@RequestParam(value = "productId", required = false) String productId,
Map<String, Object> map) {
if (!StringUtils.isEmpty(productId)) {
ProductInfo productInfo = productService.findOne(productId);
map.put("productInfo", productInfo);
}

//查询所有商品类目
List<ProductCategory> categoryList = categoryService.findAll();
map.put("categoryList", categoryList);

return new ModelAndView("product/index", map);
}

@PostMapping("/save")
public ModelAndView save(@Valid ProductForm form,
BindingResult bindingResult,
Map<String, Object> map) {
if (bindingResult.hasErrors()) {
map.put("msg", bindingResult.getFieldError().getDefaultMessage());
map.put("url", "/sell/seller/product/index");
return new ModelAndView("common/error", map);
}

ProductInfo productInfo = new ProductInfo();
try {
if (StringUtils.isEmpty(form.getProductId())) {
form.setProductId(KeyUtil.genUniqueKey());
productInfo.setProductStatus(ProductStatusEnum.DOWN.getCode());
} else {
productInfo = productService.findOne(form.getProductId());
}

BeanUtils.copyProperties(form, productInfo);
productService.save(productInfo);
} catch (SellException e) {
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/product/index");
return new ModelAndView("common/error", map);
}

map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/success", map);
}
}

WechatController

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.imooc.controller;

import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.net.URLEncoder;

@Controller
@Slf4j
@RequestMapping("/wechat")
public class WechatController {

@Autowired
private WxMpService wxMpService;

@GetMapping("/authorize")
public String authorize(@RequestParam("returnUrl") String returnUrl) {
//1. 配置
//2. 调用方法
String url = "http://kwang.natapp1.cc/sell/wechat/userInfo";
String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAUTH2_SCOPE_BASE, URLEncoder.encode(returnUrl));
return "redirect:" + redirectUrl;
}

@GetMapping("/userInfo")
public String userInfo(@RequestParam("code") String code,
@RequestParam("state") String returnUrl) {
WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();
try {
wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
} catch (WxErrorException e) {
log.error("【微信网页授权】{}", e);
throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
}

String openId = wxMpOAuth2AccessToken.getOpenId();

return "redirect:" + returnUrl + "?openid=" + openId;
}

}

WeixinController

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
package com.imooc.controller;

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@RestController
@RequestMapping("/weixin")
@Slf4j
public class WeixinController {

// 不使用第三方SDK,手工获取access_token和openid

@GetMapping("/auth")
public void auth(@RequestParam("code") String code) {

// 1. 获取code
// https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx1e582ce44016633f&redirect_uri=http://kwang.natapp1.cc/sell/weixin/auth&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

log.info("进入auth方法。。。");
log.info("code = {}", code);

// 2. 获取access_token和openid
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx1e582ce44016633f&secret=12756683d0002884e2f2e7ad2f87e758&code="
+ code + "&grant_type=authorization_code";

RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);

log.info("微信返回token: response={}", response);

Gson gs = new Gson();
Map<String, String> map = gs.fromJson(response, Map.class);
log.info("access_token={}", map.get("access_token"));
log.info("openid={}", map.get("openid"));

// 3. 获取微信用户详细信息
url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + map.get("access_token")
+ "&openid=" + map.get("openid") + "&lang=zh_CN";

response = restTemplate.getForObject(url, String.class);
log.info("微信返回用户详细信息: response={}", response);
}
}

VO视图层

引入VO对象主要是解决更容易返回前端所需要的数据结构。
要返回的数据格式如下:

第一层VO - ResultVO

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

import lombok.Data;

/**
* http请求返回的最外层对象
*/
@Data
public class ResultVO<T> {

/* 状态码 */
private Integer code;

/* 提示信息 */
private String msg;

/* 具体内容 */
private T data;
}

第二层VO - ProductVO

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

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;

/**
* 商品(包含类目)
*/
@Data
public class ProductVO {

@JsonProperty("name")
private String categoryName;

@JsonProperty("type")
private Integer categoryType;

@JsonProperty("foods")
private List<ProductInfoVO> productInfoVOList;
}

第三层VO - ProductInfoVO

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
package com.imooc.VO;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;

/**
* 商品详情
*/
@Data
public class ProductInfoVO {

@JsonProperty("id")
private String productId;

@JsonProperty("name")
private String productName;

@JsonProperty("price")
private BigDecimal productPrice;

@JsonProperty("description")
private String productDescription;

@JsonProperty("icon")
private String productIcon;
}

DTO层

引入DTO对象主要是为了返回的数据结构与实体对象的数据结构不匹配的问题。
可以简单理解成数据库视图。

OrderDTO

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.imooc.dto;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.imooc.dataobject.OrderDetail;
import com.imooc.enums.OrderStatusEnum;
import com.imooc.enums.PayStatusEnum;
import com.imooc.utils.EnumUtil;
import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class OrderDTO {

/**
* 订单id.
*/
private String orderId;

/**
* 买家名字.
*/
private String buyerName;

/**
* 买家手机号.
*/
private String buyerPhone;

/**
* 买家地址.
*/
private String buyerAddress;

/**
* 买家微信Openid.
*/
private String buyerOpenid;

/**
* 订单总金额.
*/
private BigDecimal orderAmount;

/**
* 订单状态, 默认为0新下单.
*/
private Integer orderStatus;

/**
* 支付状态, 默认为0未支付.
*/
private Integer payStatus;

/**
* 创建时间.
*/
// @JsonSerialize(using = Date2LongSerializer.class)
private Date createTime;

/**
* 更新时间.
*/
// @JsonSerialize(using = Date2LongSerializer.class)
private Date updateTime;

List<OrderDetail> orderDetailList;

@JsonIgnore
public OrderStatusEnum getOrderStatusEnum() {
return EnumUtil.getByCode(orderStatus, OrderStatusEnum.class);
}

@JsonIgnore
public PayStatusEnum getPayStatusEnum() {
return EnumUtil.getByCode(payStatus, PayStatusEnum.class);
}
}

CartDTO

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

import lombok.Data;

@Data
public class CartDTO {

private String productId;

private Integer productQuantity;

public CartDTO(String productId, Integer productQuantity) {
this.productId = productId;
this.productQuantity = productQuantity;
}
}

Form类

CategoryForm

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

import lombok.Data;

@Data
public class CategoryForm {

private Integer categoryId;

/**
* 类目名称.
*/
private String categoryName;

/**
* 类目编号.
*/
private Integer categoryType;
}

ProductForm

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
package com.imooc.form;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class ProductForm {

private String productId;

/** 名字. */
private String productName;

/** 单价. */
private BigDecimal productPrice;

/** 库存. */
private Integer productStock;

/** 描述. */
private String productDescription;

/** 小图. */
private String productIcon;

/** 类目编号. */
private Integer categoryType;
}

OrderForm

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
package com.imooc.form;

import lombok.Data;

import javax.validation.constraints.NotEmpty;

@Data
public class OrderForm {

/**
* 买家姓名
*/
@NotEmpty(message = "买家姓名不能为空")
private String name;

/**
* 买家电话
*/
@NotEmpty(message = "买家电话不能为空")
private String phone;

/**
* 买家地址
*/
@NotEmpty(message = "买家地址不能为空")
private String address;

/**
* 买家openid
*/
@NotEmpty(message = "买家openid不能为空")
private String openid;

/**
* 买家购物车
*/
@NotEmpty(message = "购物车不能为空")
private String items;
}

Exception异常处理

自定义异常

SellException

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

import com.imooc.enums.ResultEnum;

public class SellException extends RuntimeException {

private Integer code;

public SellException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}

public SellException(Integer code, String message) {
super(message);
this.code = code;
}
}

ResponseBankException

1
2
3
4
package com.imooc.exception;

public class ResponseBankException extends RuntimeException {
}

SellerAuthorizeException

1
2
3
4
package com.imooc.exception;

public class SellerAuthorizeException extends RuntimeException {
}

自定义异常处理器

SellExceptionHandler

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
package com.imooc.handler;

import com.imooc.VO.ResultVO;
import com.imooc.config.ProjectUrlConfig;
import com.imooc.exception.ResponseBankException;
import com.imooc.exception.SellException;
import com.imooc.exception.SellerAuthorizeException;
import com.imooc.utils.ResultVOUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class SellExceptionHandler {

@Autowired
private ProjectUrlConfig projectUrlConfig;

//拦截登录异常
//http://sell.natapp4.cc/sell/wechat/qrAuthorize?returnUrl=http://sell.natapp4.cc/sell/seller/login
@ExceptionHandler(value = SellerAuthorizeException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ModelAndView handlerAuthorizeException() {
return new ModelAndView("redirect:"
.concat(projectUrlConfig.getWechatOpenAuthorize())
.concat("/sell/wechat/qrAuthorize")
.concat("?returnUrl=")
.concat(projectUrlConfig.getSell())
.concat("/sell/seller/login"));
}

@ExceptionHandler(value = SellException.class)
@ResponseBody
public ResultVO handlerSellerException(SellException e) {
return ResultVOUtil.error(e.getCode(), e.getMessage());
}

@ExceptionHandler(value = ResponseBankException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public void handleResponseBankException() {

}
}

统一异常处理

ThymeleafTest

假设访问一个不存在的页面,抛出异常

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

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ThymeleafTest {

@ResponseBody
@RequestMapping("/hello")
public String hello() throws Exception {
throw new Exception("发生错误");
}
}

创建全局异常处理类

通过使用@ControllerAdvice定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html中。

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.mindex.exception;

import com.mindex.entities.ErrorInfo;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;

@ControllerAdvice
public class GlobalExceptionHandler {

public static final String DEFAULT_ERROR_VIEW = "error";

@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}

实现error.html页面展示:在templates目录下创建error.html,将请求的URL和Exception对象的message输出。

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head lang="en">
<meta charset="UTF-8"/>
<title>统一异常处理</title>
</head>
<body>
<h1>Error Handler</h1>
<div th:text="${url}"></div>
<div th:text="${exception.message}"></div>
</body>
</html>

启动该应用,访问:http://localhost:8080/hello,可以看到如下错误提示页面。

通过实现上述内容之后,我们只需要在Controller中抛出Exception,当然我们可能会有多种不同的Exception。然后在@ControllerAdvice类中,根据抛出的具体Exception类型匹配@ExceptionHandler中配置的异常类型来匹配错误映射和处理。

返回json格式

创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mindex.entities;

import lombok.Data;

@Data
public class ErrorInfo<T> {

public static final Integer OK = 0;
public static final Integer ERROR = 100;

private Integer code;
private String message;
private String url;
private T data;
}

创建一个自定义异常,用来实验捕获该异常,并返回json。

1
2
3
4
5
6
7
8
package com.mindex.exception;

public class MyException extends Exception {

public MyException(String message) {
super(message);
}
}

Controller中增加json映射,抛出MyException异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mindex.controller;

import com.mindex.exception.MyException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloController {

@RequestMapping("/json")
public String json() throws MyException {
throw new MyException("发生错误2");
}

}

MyException异常创建对应的处理。

1
2
3
4
5
6
7
8
9
10
@ExceptionHandler(value = MyException.class)
@ResponseBody
public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, MyException e) throws Exception {
ErrorInfo<String> r = new ErrorInfo<>();
r.setMessage(e.getMessage());
r.setCode(ErrorInfo.ERROR);
r.setData("Some Data");
r.setUrl(req.getRequestURL().toString());
return r;
}

启动应用,访问:

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

![](http://ws1.sinaimg.cn/large/006By2pOgy1fqtwhe1igxj30xy0cmdif.jpg)


---
## Authorize用户有效性鉴定
### SellerAuthorizeAspect
```java
package com.imooc.aspect;

import com.imooc.constant.CookieConstant;
import com.imooc.constant.RedisConstant;
import com.imooc.exception.SellerAuthorizeException;
import com.imooc.utils.CookieUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {

@Autowired
private StringRedisTemplate redisTemplate;

@Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
"&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
public void verify() {
}

@Before("verify()")
public void doVerify() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

//查询cookie
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie == null) {
log.warn("【登录校验】Cookie中查不到token");
throw new SellerAuthorizeException();
}

//去redis里查询
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if (StringUtils.isEmpty(tokenValue)) {
log.warn("【登录校验】Redis中查不到token");
throw new SellerAuthorizeException();
}
}
}


使用Mabatis注解方式实现增删改查

mapper层

ProductCategoryMapper

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
package com.imooc.dataobject.mapper;

import com.imooc.dataobject.ProductCategory;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;

public interface ProductCategoryMapper {

@Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{category_type, jdbcType=INTEGER})")
int insertByMap(Map<String, Object> map);

@Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{categoryType, jdbcType=INTEGER})")
int insertByObject(ProductCategory productCategory);

@Select("select * from product_category where category_type = #{categoryType}")
@Results({
@Result(column = "category_id", property = "categoryId"),
@Result(column = "category_name", property = "categoryName"),
@Result(column = "category_type", property = "categoryType")
})
ProductCategory findByCategoryType(Integer categoryType);

@Select("select * from product_category where category_name = #{categoryName}")
@Results({
@Result(column = "category_id", property = "categoryId"),
@Result(column = "category_name", property = "categoryName"),
@Result(column = "category_type", property = "categoryType")
})
List<ProductCategory> findByCategoryName(String categoryName);

@Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}")
int updateByCategoryType(@Param("categoryName") String categoryName,
@Param("categoryType") Integer categoryType);

@Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}")
int updateByObject(ProductCategory productCategory);

@Delete("delete from product_category where category_type = #{categoryType}")
int deleteByCategoryType(Integer categoryType);

ProductCategory selectByCategoryType(Integer categoryType);
}

mapper单元测试

ProductCategoryMapperTest

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
package com.imooc.dataobject.mapper;

import com.imooc.dataobject.ProductCategory;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class ProductCategoryMapperTest {

@Autowired
private ProductCategoryMapper mapper;

@Test
public void insertByMap() throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("categoryName", "师兄最不爱");
map.put("category_type", 101);
int result = mapper.insertByMap(map);
Assert.assertEquals(1, result);
}

@Test
public void insertByObject() {
ProductCategory productCategory = new ProductCategory();
productCategory.setCategoryName("师兄最不爱");
productCategory.setCategoryType(102);
int result = mapper.insertByObject(productCategory);
Assert.assertEquals(1, result);
}

@Test
public void findByCategoryType() {
ProductCategory result = mapper.findByCategoryType(102);
Assert.assertNotNull(result);
}

@Test
public void findByCategoryName() {
List<ProductCategory> result = mapper.findByCategoryName("师兄最不爱");
Assert.assertNotEquals(0, result.size());
}

@Test
public void updateByCategoryType() {
int result = mapper.updateByCategoryType("师兄最不爱的分类", 102);
Assert.assertEquals(1, result);
}

@Test
public void updateByObject() {
ProductCategory productCategory = new ProductCategory();
productCategory.setCategoryName("师兄最不爱");
productCategory.setCategoryType(102);
int result = mapper.updateByObject(productCategory);
Assert.assertEquals(1, result);
}

@Test
public void deleteByCategoryType() {
int result = mapper.deleteByCategoryType(102);
Assert.assertEquals(1, result);
}

@Test
public void selectByCategoryType() {
ProductCategory productCategory = mapper.selectByCategoryType(101);
Assert.assertNotNull(productCategory);
}

}

DAO层

ProductCategoryDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.imooc.dataobject.dao;

import com.imooc.dataobject.mapper.ProductCategoryMapper;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Map;

public class ProductCategoryDao {

@Autowired
ProductCategoryMapper mapper;

public int insertByMap(Map<String, Object> map) {
return mapper.insertByMap(map);
}
}

对象转换

Form表单对象(Json)转成DTO

OrderForm2OrderDTOConverter

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
package com.imooc.converter;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.imooc.dataobject.OrderDetail;
import com.imooc.dto.OrderDTO;
import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import com.imooc.form.OrderForm;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class OrderForm2OrderDTOConverter {

public static OrderDTO convert(OrderForm orderForm) {
Gson gson = new Gson();
OrderDTO orderDTO = new OrderDTO();

orderDTO.setBuyerName(orderForm.getName());
orderDTO.setBuyerPhone(orderForm.getPhone());
orderDTO.setBuyerAddress(orderForm.getAddress());
orderDTO.setBuyerOpenid(orderForm.getOpenid());

List<OrderDetail> orderDetailList = new ArrayList<>();
try {
orderDetailList = gson.fromJson(orderForm.getItems(),
new TypeToken<List<OrderDetail>>() {
}.getType());
} catch (Exception e) {
log.error("【对象转换】错误, string={}", orderForm.getItems());
throw new SellException(ResultEnum.PARAM_ERROR);
}
orderDTO.setOrderDetailList(orderDetailList);

return orderDTO;
}
}

Data Object转DTO

OrderMaster2OrderDTOConverter

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

import com.imooc.dataobject.OrderMaster;
import com.imooc.dto.OrderDTO;
import org.springframework.beans.BeanUtils;
import java.util.List;
import java.util.stream.Collectors;

public class OrderMaster2OrderDTOConverter {

public static OrderDTO convert(OrderMaster orderMaster) {
OrderDTO orderDTO = new OrderDTO();
BeanUtils.copyProperties(orderMaster, orderDTO);
return orderDTO;
}

public static List<OrderDTO> convert(List<OrderMaster> orderMasterList) {
return orderMasterList.stream().map(e ->
convert(e)
).collect(Collectors.toList());
}
}

中文字符乱码

application.properties

1
2
3
4
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8

IDEA设置

网页模板

Freemarker

common/header.ftl

1
2
3
4
5
6
<head>
<meta charset="utf-8">
<title>卖家后端管理系统</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/sell/css/style.css">
</head>

common/nav.ftl

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
<nav class="navbar navbar-inverse navbar-fixed-top" id="sidebar-wrapper" role="navigation">
<ul class="nav sidebar-nav">
<li class="sidebar-brand">
<a href="#">
卖家管理系统
</a>
</li>
<li>
<a href="/sell/seller/order/list"><i class="fa fa-fw fa-list-alt"></i> 订单</a>
</li>
<li class="dropdown open">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true"><i class="fa fa-fw fa-plus"></i> 商品 <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li class="dropdown-header">操作</li>
<li><a href="/sell/seller/product/list">列表</a></li>
<li><a href="/sell/seller/product/index">新增</a></li>
</ul>
</li>
<li class="dropdown open">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true"><i class="fa fa-fw fa-plus"></i> 类目 <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li class="dropdown-header">操作</li>
<li><a href="/sell/seller/category/list">列表</a></li>
<li><a href="/sell/seller/category/index">新增</a></li>
</ul>
</li>

<li>
<a href="/sell/seller/logout"><i class="fa fa-fw fa-list-alt"></i> 登出</a>
</li>
</ul>
</nav>

common/success.ftl

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
<html>
<head>
<title>成功提示</title>
<meta charset="UTF-8">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="alert alert-dismissable alert-success">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4>
成功!
</h4>
<strong>${msg!""}</strong>
<a href="${url}" class="alert-link">3s后自动跳转</a>
</div>
</div>
</div>
</div>
</body>
<script>
setTimeout('location.href="${url}"', 3000);
</script>
</html>

common/error.ftl

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
<html>
<head>
<title>错误提示</title>
<meta charset="UTF-8">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="alert alert-dismissable alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4>
错误!
</h4>
<strong>${msg}</strong>
<a href="${url}" class="alert-link">3s后自动跳转</a>
</div>
</div>
</div>
</div>
</body>
<script>
setTimeout('location.href="${url}"', 3000);
</script>
</html>

category/index.ftl

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
<html>
<#include "../common/header.ftl">

<body>
<div id="wrapper" class="toggled">

<#--边栏sidebar-->
<#include "../common/nav.ftl">

<#--主要内容content-->
<div id="page-content-wrapper">
<div class="container-fluid">
<div class="row clearfix">
<div class="col-md-12 column">
<form role="form" method="post" action="/sell/seller/category/save">
<div class="form-group">
<label>名字</label>
<input name="categoryName" type="text" class="form-control" value="${(category.categoryName)!''}"/>
</div>
<div class="form-group">
<label>类别</label>
<input name="categoryType" type="number" class="form-control" value="${(category.categoryType)!''}"/>
</div>
<input hidden type="text" name="categoryId" value="${(category.categoryId)!''}">
<button type="submit" class="btn btn-default">提交</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

category/list.ftl

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
<html>
<#include "../common/header.ftl">

<body>
<div id="wrapper" class="toggled">

<#--边栏sidebar-->
<#include "../common/nav.ftl">

<#--主要内容content-->
<div id="page-content-wrapper">
<div class="container-fluid">
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>类目id</th>
<th>名字</th>
<th>类别</th>
<th>创建时间</th>
<th>修改时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>

<#list categoryList as category>
<tr>
<td>${category.categoryId}</td>
<td>${category.categoryName}</td>
<td>${category.categoryType}</td>
<td>${category.createTime}</td>
<td>${category.updateTime}</td>
<td><a href="/sell/seller/category/index?categoryId=${category.categoryId}">修改</a></td>
</tr>
</#list>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

product/index.ftl

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
<html>
<#include "../common/header.ftl">

<body>
<div id="wrapper" class="toggled">

<#--边栏sidebar-->
<#include "../common/nav.ftl">

<#--主要内容content-->
<div id="page-content-wrapper">
<div class="container-fluid">
<div class="row clearfix">
<div class="col-md-12 column">
<form role="form" method="post" action="/sell/seller/product/save">
<div class="form-group">
<label>名称</label>
<input name="productName" type="text" class="form-control" value="${(productInfo.productName)!''}"/>
</div>
<div class="form-group">
<label>价格</label>
<input name="productPrice" type="number" class="form-control" value="${(productInfo.productPrice)!''}"/>
</div>
<div class="form-group">
<label>库存</label>
<input name="productStock" type="number" class="form-control" value="${(productInfo.productStock)!''}"/>
</div>
<div class="form-group">
<label>描述</label>
<input name="productDescription" type="text" class="form-control" value="${(productInfo.productDescription)!''}"/>
</div>
<div class="form-group">
<label>图片</label>
<input name="productIcon" type="text" class="form-control" value="${(productInfo.productIcon)!''}"/>
</div>
<div class="form-group">
<label>类目</label>
<select name="categoryType" class="form-control">
<#list categoryList as category>
<option value="${category.categoryType}"
<#if (productInfo.categoryType)?? && productInfo.categoryType == category.categoryType>
selected
</#if>
>${category.categoryName}
</option>
</#list>
</select>
</div>
<#-- <input type="hidden" name="productStatus" value="${(productInfo.productStatus)!0}">-->
<input type="hidden" name="productId" value="${(productInfo.productId)!''}">
<button type="submit" class="btn btn-default">提交</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

product/list.ftl

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
<html>
<#include "../common/header.ftl">

<body>
<div id="wrapper" class="toggled">

<#--边栏sidebar-->
<#include "../common/nav.ftl">

<#--主要内容content-->
<div id="page-content-wrapper">
<div class="container-fluid">
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>商品id</th>
<th>名称</th>
<th>图片</th>
<th>单价</th>
<th>库存</th>
<th>描述</th>
<th>类目</th>
<th>创建时间</th>
<th>修改时间</th>
<th colspan="2">操作</th>
</tr>
</thead>
<tbody>

<#list productInfoPage.content as productInfo>
<tr>
<td>${productInfo.productId}</td>
<td>${productInfo.productName}</td>
<td><img height="100" width="100" src="${productInfo.productIcon}" alt=""></td>
<td>${productInfo.productPrice}</td>
<td>${productInfo.productStock}</td>
<td>${productInfo.productDescription}</td>
<td>${productInfo.categoryType}</td>
<td>${productInfo.createTime}</td>
<td>${productInfo.updateTime}</td>
<td><a href="/sell/seller/product/index?productId=${productInfo.productId}">修改</a></td>
<td>
<#if productInfo.getProductStatusEnum().message == "上架">
<a href="/sell/seller/product/off_sale?productId=${productInfo.productId}">下架</a>
<#else>
<a href="/sell/seller/product/on_sale?productId=${productInfo.productId}">上架</a>
</#if>
</td>
</tr>
</#list>
</tbody>
</table>
</div>

<#--分页-->
<div class="col-md-12 column">
<ul class="pagination pull-right">
<#if currentPage lte 1>
<li class="disabled"><a href="#">上一页</a></li>
<#else>
<li><a href="/sell/seller/product/list?page=${currentPage - 1}&size=${size}">上一页</a></li>
</#if>

<#list 1..productInfoPage.getTotalPages() as index>
<#if currentPage == index>
<li class="disabled"><a href="#">${index}</a></li>
<#else>
<li><a href="/sell/seller/product/list?page=${index}&size=${size}">${index}</a></li>
</#if>
</#list>

<#if currentPage gte productInfoPage.getTotalPages()>
<li class="disabled"><a href="#">下一页</a></li>
<#else>
<li><a href="/sell/seller/product/list?page=${currentPage + 1}&size=${size}">下一页</a></li>
</#if>
</ul>
</div>
</div>
</div>
</div>

</div>
</body>
</html>

order/list.ftl

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
<html>
<#include "../common/header.ftl">

<body>
<div id="wrapper" class="toggled">

<#--边栏sidebar-->
<#include "../common/nav.ftl">

<#--主要内容content-->
<div id="page-content-wrapper">
<div class="container-fluid">
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>订单id</th>
<th>姓名</th>
<th>手机号</th>
<th>地址</th>
<th>金额</th>
<th>订单状态</th>
<th>支付状态</th>
<th>创建时间</th>
<th colspan="2">操作</th>
</tr>
</thead>
<tbody>
<#list orderDTOPage.content as orderDTO>
<tr>
<th>${orderDTO.orderId}</th>
<th>${orderDTO.buyerName}</th>
<th>${orderDTO.buyerPhone}</th>
<th>${orderDTO.buyerAddress}</th>
<th>${orderDTO.orderAmount}</th>
<th>${orderDTO.getOrderStatusEnum().msg}</th>
<th>${orderDTO.getPayStatusEnum().msg}</th>
<th>${orderDTO.createTime}</th>
<th><a href="/sell/seller/order/detail?orderId=${orderDTO.orderId}">详情</a></th>
<th>
<#if orderDTO.getOrderStatusEnum().msg == "新订单">
<a href="/sell/seller/order/cancel?orderId=${orderDTO.orderId}">取消</a>
</#if>
</th>
</tr>
</#list>
</tbody>
</table>
</div>
</div>
<div class="col-md-12 column">
<ul class="pagination pull-right">
<#if currentPage lte 1>
<li class="disabled"><a href="#">上一页</a></li>
<#else >
<li><a href="/sell/seller/order/list?page=${currentPage - 1}&size=${size}">上一页</a></li>
</#if>
<#list 1..orderDTOPage.getTotalPages() as index>
<#if currentPage == index>
<li class="disabled"><a href="#">${index}</a></li>
<#else>
<li><a href="/sell/seller/order/list?page=${index}&size=${size}">${index}</a></li>
</#if>
</#list>
<#if currentPage gte orderDTOPage.getTotalPages()>
<li class="disabled"><a href="#">下一页</a></li>
<#else >
<li><a href="/sell/seller/order/list?page=${currentPage + 1}&size=${size}">下一页</a></li>
</#if>
</ul>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

order/detail.ftl

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
<html>
<#include "../common/header.ftl">

<body>
<div id="wrapper" class="toggled">

<#--边栏sidebar-->
<#include "../common/nav.ftl">

<#--主要内容content-->
<div id="page-content-wrapper">
<div class="container-fluid">
<div class="row clearfix">
<div class="col-md-4 column">
<table class="table table-bordered">
<thead>
<tr>
<th>订单id</th>
<th>订单总金额</th>
</tr>
</thead>
<tbody>
<tr>
<td>${orderDTO.orderId}</td>
<td>${orderDTO.orderAmount}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-12 column">
<table class="table table-bordered">
<thead>
<tr>
<th>商品id</th>
<th>商品名称</th>
<th>价格</th>
<th>数量</th>
<th>总金额</th>
</tr>
</thead>
<tbody>
<#list orderDTO.orderDetailList as orderDetail>
<tr>
<td>${orderDetail.productId}</td>
<td>${orderDetail.productName}</td>
<td>${orderDetail.productPrice}</td>
<td>${orderDetail.productQuantity}</td>
<td>${orderDetail.productPrice * orderDetail.productQuantity}</td>
</tr>
</#list>
</tbody>
</table>
</div>
<#if orderDTO.getOrderStatusEnum().msg == "新订单">
<div class="col-md-12 column">
<a href="/sell/seller/order/finish?orderId=${orderDTO.orderId}" type="button"
class="btn btn-default btn-primary">完结订单</a>
<a href="/sell/seller/order/cancel?orderId=${orderDTO.orderId}" type="button"
class="btn btn-default btn-danger">取消订单</a>
</div>
</#if>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

CSS样式

static/css

style.css
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
body {
position: relative;
overflow-x: hidden;
}
body,
html {
height: 100%;
/*background-color: #583e7e;*/
}
.nav .open > a {
background-color: transparent;
}
.nav .open > a:hover {
background-color: transparent;
}
.nav .open > a:focus {
background-color: transparent;
}
/*-------------------------------*/
/* Wrappers */
/*-------------------------------*/
#wrapper {
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
-webkit-transition: all 0.5s ease;
padding-left: 0;
transition: all 0.5s ease;
}
#wrapper.toggled {
padding-left: 180px;
}
#wrapper.toggled #sidebar-wrapper {
width: 180px;
}
#sidebar-wrapper {
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
-webkit-transition: all 0.5s ease;
background: #1a1a1a;
height: 100%;
left: 220px;
margin-left: -220px;
overflow-x: hidden;
overflow-y: auto;
transition: all 0.5s ease;
width: 0;
z-index: 1000;
}
#sidebar-wrapper::-webkit-scrollbar {
display: none;
}
#page-content-wrapper {
padding-top: 70px;
width: 100%;
}
/*-------------------------------*/
/* Sidebar nav styles */
/*-------------------------------*/
.sidebar-nav {
list-style: none;
margin: 0;
padding: 0;
position: absolute;
top: 0;
width: 220px;
}
.sidebar-nav li {
display: inline-block;
line-height: 20px;
position: relative;
width: 100%;
}
.sidebar-nav li:before {
background-color: #1c1c1c;
content: '';
height: 100%;
left: 0;
position: absolute;
top: 0;
-webkit-transition: width 0.2s ease-in;
transition: width 0.2s ease-in;
width: 3px;
z-index: -1;
}
.sidebar-nav li:first-child a {
background-color: #1a1a1a;
color: #ffffff;
}
.sidebar-nav li:nth-child(2):before {
background-color: #402d5c;
}
.sidebar-nav li:nth-child(3):before {
background-color: #4c366d;
}
.sidebar-nav li:nth-child(4):before {
background-color: #583e7e;
}
.sidebar-nav li:nth-child(5):before {
background-color: #64468f;
}
.sidebar-nav li:nth-child(6):before {
background-color: #704fa0;
}
.sidebar-nav li:nth-child(7):before {
background-color: #7c5aae;
}
.sidebar-nav li:nth-child(8):before {
background-color: #8a6cb6;
}
.sidebar-nav li:nth-child(9):before {
background-color: #987dbf;
}
.sidebar-nav li:hover:before {
-webkit-transition: width 0.2s ease-in;
transition: width 0.2s ease-in;
width: 100%;
}
.sidebar-nav li a {
color: #dddddd;
display: block;
padding: 10px 15px 10px 30px;
text-decoration: none;
}
.sidebar-nav li.open:hover before {
-webkit-transition: width 0.2s ease-in;
transition: width 0.2s ease-in;
width: 100%;
}
.sidebar-nav .dropdown-menu {
background-color: #222222;
border-radius: 0;
border: none;
box-shadow: none;
margin: 0;
padding: 0;
position: relative;
width: 100%;
}
.sidebar-nav li a:hover,
.sidebar-nav li a:active,
.sidebar-nav li a:focus,
.sidebar-nav li.open a:hover,
.sidebar-nav li.open a:active,
.sidebar-nav li.open a:focus {
background-color: transparent;
color: #ffffff;
text-decoration: none;
}
.sidebar-nav > .sidebar-brand {
font-size: 20px;
height: 65px;
line-height: 44px;
}
/*-------------------------------*/
/* Hamburger-Cross */
/*-------------------------------*/
.hamburger {
background: transparent;
border: none;
display: block;
height: 32px;
margin-left: 15px;
position: fixed;
top: 20px;
width: 32px;
z-index: 999;
}
.hamburger:hover {
outline: none;
}
.hamburger:focus {
outline: none;
}
.hamburger:active {
outline: none;
}
.hamburger.is-closed:before {
-webkit-transform: translate3d(0, 0, 0);
-webkit-transition: all 0.35s ease-in-out;
color: #ffffff;
content: '';
display: block;
font-size: 14px;
line-height: 32px;
opacity: 0;
text-align: center;
width: 100px;
}
.hamburger.is-closed:hover before {
-webkit-transform: translate3d(-100px, 0, 0);
-webkit-transition: all 0.35s ease-in-out;
display: block;
opacity: 1;
}
.hamburger.is-closed:hover .hamb-top {
-webkit-transition: all 0.35s ease-in-out;
top: 0;
}
.hamburger.is-closed:hover .hamb-bottom {
-webkit-transition: all 0.35s ease-in-out;
bottom: 0;
}
.hamburger.is-closed .hamb-top {
-webkit-transition: all 0.35s ease-in-out;
background-color: rgba(255, 255, 255, 0.7);
top: 5px;
}
.hamburger.is-closed .hamb-middle {
background-color: rgba(255, 255, 255, 0.7);
margin-top: -2px;
top: 50%;
}
.hamburger.is-closed .hamb-bottom {
-webkit-transition: all 0.35s ease-in-out;
background-color: rgba(255, 255, 255, 0.7);
bottom: 5px;
}
.hamburger.is-closed .hamb-top,
.hamburger.is-closed .hamb-middle,
.hamburger.is-closed .hamb-bottom,
.hamburger.is-open .hamb-top,
.hamburger.is-open .hamb-middle,
.hamburger.is-open .hamb-bottom {
height: 4px;
left: 0;
position: absolute;
width: 100%;
}
.hamburger.is-open .hamb-top {
-webkit-transform: rotate(45deg);
-webkit-transition: -webkit-transform 0.2s cubic-bezier(0.73, 1, 0.28, 0.08);
background-color: #ffffff;
margin-top: -2px;
top: 50%;
}
.hamburger.is-open .hamb-middle {
background-color: #ffffff;
display: none;
}
.hamburger.is-open .hamb-bottom {
-webkit-transform: rotate(-45deg);
-webkit-transition: -webkit-transform 0.2s cubic-bezier(0.73, 1, 0.28, 0.08);
background-color: #ffffff;
margin-top: -2px;
top: 50%;
}
.hamburger.is-open:before {
-webkit-transform: translate3d(0, 0, 0);
-webkit-transition: all 0.35s ease-in-out;
color: #ffffff;
content: '';
display: block;
font-size: 14px;
line-height: 32px;
opacity: 0;
text-align: center;
width: 100px;
}
.hamburger.is-open:hover before {
-webkit-transform: translate3d(-100px, 0, 0);
-webkit-transition: all 0.35s ease-in-out;
display: block;
opacity: 1;
}

Thymeleaf

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

...

<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>

index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<h1 th:text="${host}">Hello World</h1>
</body>
</html>

注意:模板的位置可以直接放在src/main/resources/templates/目录下。

application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8

# Enable template caching.
spring.thymeleaf.cache=true
# Check that the templates location exists.
spring.thymeleaf.check-template-location=true
# Content-Type value.
spring.thymeleaf.content-type=text/html
# Enable MVC Thymeleaf view resolution.
spring.thymeleaf.enabled=true
# Template encoding.
spring.thymeleaf.encoding=UTF-8
# Comma-separated list of view names that should be excluded from resolution.
spring.thymeleaf.excluded-view-names=
# Template mode to be applied to templates. See also StandardTemplateModeHandlers.
spring.thymeleaf.mode=HTML5
# Prefix that gets prepended to view names when building a URL.
spring.thymeleaf.prefix=classpath:/templates/
# Suffix that gets appended to view names when building a URL.
spring.thymeleaf.suffix=.html

ThymeleafTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mindex.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ThymeleafTest {

@RequestMapping("/")
public String index(ModelMap map) {
map.addAttribute("host", "http://www.mindex.com");
return "index";
}
}

运行结果如下:

数据库操作

JDBC方式 - JdbcTemplate

引入POM依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>

修改配置文件application.properties

1
2
3
4
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=welcome
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Service接口:UserService.java

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

public interface UserService {

void create(String name, Integer age);

void deleteByName(String name);

Integer getAllUsers();

void deleteAllUsers();
}

Service实现:UserServiceImpl.java

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.mindex.service.impl;

import com.mindex.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void create(String name, Integer age) {
jdbcTemplate.update("INSERT INTO user (name, age) VALUES (?,?);", name, age);
}

@Override
public void deleteByName(String name) {
jdbcTemplate.update("delete from USER where NAME = ?", name);
}

@Override
public Integer getAllUsers() {
return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
}

@Override
public void deleteAllUsers() {
jdbcTemplate.update("delete from USER");
}
}

单元测试:UserServiceImplTest.java

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
package com.mindex.service.impl;

import com.mindex.service.UserService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserServiceImplTest {

@Autowired
private UserService userService;

@Test
public void create() throws Exception {
// 插入5个用户
userService.create("a", 1);
userService.create("b", 2);
userService.create("c", 3);
userService.create("d", 4);
userService.create("e", 5);

Assert.assertEquals(5, userService.getAllUsers().intValue());
}

@Test
public void deleteByName() throws Exception {
userService.deleteByName("b");
userService.deleteByName("c");

Assert.assertEquals(3, userService.getAllUsers().intValue());
}

@Test
public void getAllUsers() throws Exception {
}

@Test
public void deleteAllUsers() throws Exception {
}
}

JPA方式 - Spring-data-jpa

引入pom依赖:pom.xml

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

application.properties创建数据库连接信息。

1
2
3
4
5
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123qweasd
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop

spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:

  • create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
  • create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
  • update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
  • validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

实体类(Entity)

创建一个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射到数据库表中,由于配置了hibernate.hbm2ddl.auto,在应用启动的时候框架会自动去数据库中创建对应的表。

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
package com.mindex.entities;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Data
@Entity
public class User {

@Id
@GeneratedValue
private Long id;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private Integer age;

public User(String name, Integer age) {
this.name = name;
this.age = age;
}

public User() {
}
}

数据访问接口(Repository)

UserRepository

下面针对User实体创建对应的Repository接口实现对该实体的数据访问,如下代码:UserRepository.java

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

import com.mindex.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;


public interface UserRepository extends JpaRepository<User, Long> {

User findByName(String name);

User findByNameAndAge(String name, Integer age);

@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);

}

在Spring-data-jpa中,只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类,直接减少了我们的文件清单。

下面对上面的UserRepository做一些解释,该接口继承自JpaRepository,通过查看JpaRepository接口的API文档,可以看到该接口本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再自己定义。

在我们实际开发中,JpaRepository接口定义的接口往往还不够或者性能不够优化,我们需要进一步实现更复杂一些的查询或操作。由于本文重点在spring boot中整合spring-data-jpa,在这里先抛砖引玉简单介绍一下spring-data-jpa中让我们兴奋的功能,后续再单独开篇讲一下spring-data-jpa中的常见使用。

在上例中,我们可以看到下面两个函数:

  • User findByName(String name)
  • User findByNameAndAge(String name, Integer age)

它们分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询

除了通过解析方法名来创建查询外,它也提供通过使用@Query 注解来创建查询,您只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样。

Spring-data-jpa的能力远不止本文提到的这些,由于本文主要以整合介绍为主,对于Spring-data-jpa的使用只是介绍了常见的使用方式。诸如@Modifying操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容就不在本文中详细展开,这里先挖个坑,后续再补文章填坑,如您对这些感兴趣可以关注我博客或简书,同样欢迎大家留言交流想法。

单元测试

UserRepositoryTest

在完成了上面的数据访问接口之后,按照惯例就是编写对应的单元测试来验证编写的内容是否正确。这里就不多做介绍,主要通过数据操作和查询来反复验证操作的正确性。

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.mindex.repository;

import com.mindex.entities.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Test
public void test() throws Exception {
// 创建10条记录
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));

// 测试findAll, 查询所有记录
Assert.assertEquals(10, userRepository.findAll().size());

// 测试findByName, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());

// 测试findUser, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());

// 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());

// 测试删除姓名为AAA的User
userRepository.delete(userRepository.findByName("AAA"));

// 测试findAll, 查询所有记录, 验证上面的删除是否成功
Assert.assertEquals(9, userRepository.findAll().size());
}

}

集成Redis

手动配置集成Redis

引入POM依赖:pom.xml

1
2
3
4
5
<!--此处并不是直接使用spring提供的redis-starter-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

外部配置文件:application.yaml

1
2
3
4
5
6
7
8
9
jedis:
host: 127.0.0.1
port: 6379
pool:
max-idle: 300
min-idle: 10
max-total: 600
max-wait: 1000
block-when-exhausted: true

Java配置类(替代传统xml):RedisConfig.java

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.mindex.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Data
@Component
public class RedisConfig {

@Bean("jedis.config")
public JedisPoolConfig jedisPoolConfig(@Value("${jedis.pool.min-idle}") int minIdle,
@Value("${jedis.pool.max-idle}") int maxIdle,
@Value("${jedis.pool.max-wait}") int maxWaitMillis,
@Value("${jedis.pool.block-when-exhausted}") boolean blockWhenExhausted,
@Value("${jedis.pool.max-total}") int maxTotal) {

JedisPoolConfig config = new JedisPoolConfig();
config.setMinIdle(minIdle);
config.setMaxIdle(maxIdle);
config.setMaxWaitMillis(maxWaitMillis);
config.setMaxTotal(maxTotal);
// 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
config.setBlockWhenExhausted(blockWhenExhausted);
// 是否启用pool的jmx管理功能, 默认true
config.setJmxEnabled(true);
return config;
}

@Bean
public JedisPool jedisPool(@Qualifier("jedis.config") JedisPoolConfig config,
@Value("${jedis.host}") String host,
@Value("${jedis.port}") int port) {
return new JedisPool(config, host, port);
}
}

Service接口定义:RedisService.java

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

public interface RedisService {

String get(String key);

boolean set(String key, String val);

}

Service接口实现类:RedisServiceImpl.java

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.mindex.service.impl;

import com.mindex.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Service
public class RedisServiceImpl implements RedisService {

// 此处直接注入即可
@Autowired
private JedisPool jedisPool;

@Override
public String get(String key) {
Jedis jedis = this.jedisPool.getResource();
String ret;
try {
ret = jedis.get(key);
} finally {
if (jedis != null)
jedis.close();
}
return ret;
}

@Override
public boolean set(String key, String val) {
Jedis jedis = this.jedisPool.getResource();
try {
return "OK".equals(jedis.set(key, val));
} finally {
if (jedis != null)
jedis.close();
}
}

}

测试:RedisServiceImplTest.java

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.mindex.service.impl;

import com.mindex.service.RedisService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisServiceImplTest {

@Autowired
private RedisService redisService;

@Test
public void testGet() {
// test set
boolean status = this.redisService.set("foo", "bar");
Assert.assertTrue(status);

// test get
String str = this.redisService.get("foo");
Assert.assertEquals("bar", str);
}
}

在Redis中检查结果:

使用spring-boot-starter-data-redis

外部配置文件:application.yaml

1
2
3
4
5
6
7
8
9
jedis:
host: 127.0.0.1
port: 6379
pool:
max-idle: 300
min-idle: 10
max-total: 600
max-wait: 1000
block-when-exhausted: true

配置类:RedisConfig1.java

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
package com.mindex.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig1 {

@Autowired
private RedisConnectionFactory redisConnectionFactory;

/**
* 实例化 RedisTemplate 对象
*
*/
@Bean
public RedisTemplate<String, Object> functionDomainRedisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
this.initRedisTemplate(redisTemplate, redisConnectionFactory);
return redisTemplate;
}

/**
* 序列化设置
*/
private void initRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(factory);
}

@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}

@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}

@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}

@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}

@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}

}

简单测试:RedisAnotherConfigTest.java

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
package com.mindex;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisAnotherConfigTest {

@Autowired
private ValueOperations<String, Object> valueOperations;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
public void contextLoads() {
}

@Test
public void testStringOps() {
this.valueOperations.set("k1", "spring-redis");

Boolean hasKey = this.redisTemplate.hasKey("k1");
assertEquals(true, hasKey);

Object str = this.valueOperations.get("k1");
assertNotNull(str);
assertEquals("spring-redis", str.toString());
}
}

邮件

使用JavaMailSender发送邮件

引入POM依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

增加配置:application.properties

注意:这里的password是邮箱授权码,不是邮箱密码。

1
2
3
4
5
6
spring.mail.host=smtp.qq.com
spring.mail.username=85648606@qq.com
spring.mail.password=clqpsraiifwqbidg
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true

单元测试:MailTest.java

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.mindex;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MailTest {

@Autowired
private JavaMailSender mailSender;

@Test
public void sendSimpleMail() throws Exception {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("85648606@qq.com");
message.setTo("wfgdlut@msn.com");
message.setSubject("主题:简单邮件");
message.setText("测试邮件内容");

mailSender.send(message);
}
}

发送html格式邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void sendAttachmentsMail() throws Exception {

MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage);
mimeMessageHelper.setFrom("85648606@qq.com");
mimeMessageHelper.setTo("wfgdlut@msn.com");
mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【HTML】");

StringBuilder sb = new StringBuilder();
sb.append("<html><head></head>");
sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>");
sb.append("</html>");

// 启用html
mimeMessageHelper.setText(sb.toString(), true);
// 发送邮件
mailSender.send(mimeMessage);

System.out.println("邮件已发送");

}

发送包含内嵌图片的邮件

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
/**
* 发送包含内嵌图片的邮件
*
* @throws Exception
*/
@Test
public void sendAttachedImageMail() throws Exception {
MimeMessage mimeMessage = mailSender.createMimeMessage();
// multipart模式
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
mimeMessageHelper.setFrom("85648606@qq.com");
mimeMessageHelper.setTo("wfgdlut@msn.com");
mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【图片】");

StringBuilder sb = new StringBuilder();
sb.append("<html><head></head>");
sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p>");
// cid为固定写法,imageId指定一个标识
sb.append("<img src=\"cid:imageId\"/></body>");
sb.append("</html>");

// 启用html
mimeMessageHelper.setText(sb.toString(), true);

// 设置imageId
FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png"));
mimeMessageHelper.addInline("imageId", img);

// 发送邮件
mailSender.send(mimeMessage);

System.out.println("邮件已发送");
}

发送包含附件的邮件

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
/**
* 发送包含附件的邮件
* @throws Exception
*/
@Test
public void sendAttendedFileMail() throws Exception {
MimeMessage mimeMessage = mailSender.createMimeMessage();
// multipart模式
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8");
mimeMessageHelper.setFrom("85648606@qq.com");
mimeMessageHelper.setTo("wfgdlut@msn.com");
mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【附件】");

StringBuilder sb = new StringBuilder();
sb.append("<html><head></head>");
sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>");
sb.append("</html>");

// 启用html
mimeMessageHelper.setText(sb.toString(), true);
// 设置附件
FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png"));
mimeMessageHelper.addAttachment("test.png", img);

// 发送邮件
mailSender.send(mimeMessage);

System.out.println("邮件已发送");
}

本文标题:Spring Boot 学习笔记 - 钢钢更新

文章作者:王方钢 / Kenny Wang

发布时间:2018年04月29日 - 14:04

最后更新:2019年06月14日 - 00:06

原始链接:https://wangfanggang.com/Java/Spring-Boot/springboot/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

王方钢 / Kenny Wang wechat
王方钢 | 智叟网络
0%