背景介绍 该文档是在慕课网实战课程《2019版 微服务时代Spring Boot企业微信点餐系统》基础上总结而成,旨在记录Spring Boot一些相关知识,文章中涉及的代码都经过验证,可以直接使用。 该文档作为个人参考资料,会长期更新。
慕课网课程地址:2019版 微服务时代Spring Boot企业微信点餐系统
数据库设计
微信点餐数据库 - SQL.md1 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 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 9 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 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 /> </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 > </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 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 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 { static Map<Long, User> users = Collections.synchronizedMap(new HashMap <Long, User>()); @ApiOperation(value = "获取用户列表", notes = "") @RequestMapping(value = "/", method = RequestMethod.GET) public List<User> getUserList () { 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.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) { 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) { 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.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 { @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 { 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 { 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); } 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 ; } } 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 ; 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 @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; 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 public class OrderMaster { @Id private String orderId; private String buyerName; private String buyerPhone; private String buyerAddress; private String buyerOpenid; private BigDecimal orderAmount; private Integer orderStatus = OrderStatusEnum.NEW.getCode(); 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; private String orderId; 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 = new ProductCategory ("女生最爱" , 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); for (OrderDetail orderDetail : orderDTO.getOrderDetailList()) { ProductInfo productInfo = productService.findOne(orderDetail.getProductId()); if (productInfo == null ) { throw new SellException (ResultEnum.PRODUCT_NOT_EXIST); } orderAmount = productInfo.getProductPrice() .multiply(new BigDecimal (orderDetail.getProductQuantity())) .add(orderAmount); BeanUtils.copyProperties(productInfo, orderDetail); orderDetail.setDetailId(KeyUtil.genUniqueKey()); orderDetail.setOrderId(orderId); orderDetailRepository.save(orderDetail); } 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); 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)) { } 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 () { List<ProductInfo> productInfoList = productService.findUpAll(); List<Integer> categoryTypeList = productInfoList.stream() .map(e -> e.getCategoryType()) .collect(Collectors.toList()); List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList); 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; @GetMapping("/list") public ModelAndView list (Map<String, Object> map) { List<ProductCategory> categoryList = categoryService.findAll(); map.put("categoryList" , categoryList); return new ModelAndView ("category/list" , map); } @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); } @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; @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); } @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); } @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); } @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; @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) { 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 { @GetMapping("/auth") public void auth (@RequestParam("code") String code) { log.info("进入auth方法。。。" ); log.info("code = {}" , code); 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" )); 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;@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 { private String orderId; private String buyerName; private String buyerPhone; private String buyerAddress; private String buyerOpenid; private BigDecimal orderAmount; private Integer orderStatus; private Integer payStatus; private Date createTime; 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; } }
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; }
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; }
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; @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; @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; }
启动应用,访问:http://localhost:8080/json
,可以得到如下返回内容:
Authorize用户有效性鉴定 SellerAuthorizeAspect 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 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 = CookieUtil.get(request, CookieConstant.TOKEN); if (cookie == null ) { log.warn("【登录校验】Cookie中查不到token" ); throw new SellerAuthorizeException (); } 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); } }
对象转换 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 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% ; } .nav .open > a { background-color : transparent; } .nav .open > a :hover { background-color : transparent; } .nav .open > a :focus { background-color : transparent; } #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 { 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 { 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 spring.thymeleaf.cache=true spring.thymeleaf.check-template-location=true spring.thymeleaf.content-type=text/html spring.thymeleaf.enabled=true spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.excluded-view-names= spring.thymeleaf.mode=HTML5 spring.thymeleaf.prefix=classpath:/templates/ 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 { 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的使用只是介绍了常见的使用方式。
单元测试 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 { 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 )); Assert.assertEquals(10 , userRepository.findAll().size()); Assert.assertEquals(60 , userRepository.findByName("FFF" ).getAge().longValue()); Assert.assertEquals(60 , userRepository.findUser("FFF" ).getAge().longValue()); Assert.assertEquals("FFF" , userRepository.findByNameAndAge("FFF" , 60 ).getName()); userRepository.delete(userRepository.findByName("AAA" )); Assert.assertEquals(9 , userRepository.findAll().size()); } }
集成Redis 手动配置集成Redis 引入POM依赖:pom.xml
1 2 3 4 5 <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); config.setBlockWhenExhausted(blockWhenExhausted); 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 () { boolean status = this .redisService.set("foo" , "bar" ); Assert.assertTrue(status); 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; @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.java1 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>" ); 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 @Test public void sendAttachedImageMail () throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); 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>" ); sb.append("<img src=\"cid:imageId\"/></body>" ); sb.append("</html>" ); mimeMessageHelper.setText(sb.toString(), true ); 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 @Test public void sendAttendedFileMail () throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); 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>" ); 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("邮件已发送" ); }