美文网首页
Spring Boot 学习笔记 - 钢钢更新

Spring Boot 学习笔记 - 钢钢更新

作者: 钢钢更新 | 来源:发表于2018-05-02 17:07 被阅读0次

    背景介绍

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

    慕课网课程地址:Spring Boot企业微信点餐系统

    数据库设计

    微信点餐数据库 - SQL.md

    -- 类目
    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 '卖家信息表';
    

    Spring Boot项目结构


    POM依赖

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.imooc</groupId>
        <artifactId>sell</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>sell</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.3.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.github.binarywang</groupId>
                <artifactId>weixin-java-mp</artifactId>
                <version>2.7.0</version>
            </dependency>
    
            <dependency>
                <groupId>cn.springboot</groupId>
                <artifactId>best-pay-sdk</artifactId>
                <version>1.1.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-freemarker</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-websocket</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.2.0</version>
            </dependency>
    
    
            <!--<dependency>-->
            <!--<groupId>org.springframework.boot</groupId>-->
            <!--<artifactId>spring-boot-starter-cache</artifactId>-->
            <!--</dependency>-->
        </dependencies>
    
        <build>
            <finalName>sell</finalName>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    

    应用配置

    全局配置文件

    application.yml

    spring:
      profiles:
        active: dev
    

    开发配置文件

    application-dev.yml

    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: 123456
        url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false
      jpa:
        show-sql: true
      jackson:
        default-property-inclusion: non_null
      redis:
        host: 192.168.30.113
        port: 6379
    server:
      context-path: /sell
    #logging:
    #  pattern:
    #    console: "%d - %msg%n"
    ##  path: /var/log/tomcat/
    #  file: /var/log/tomcat/sell.log
    #  level:
    #    com.imooc.LoggerTest: debug
    
    wechat:
      mpAppId: wxd898fcb01713c658
      mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx
      openAppId: wx6ad144e54af67d87
      openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx
      mchId: 1483469312
      mchKey: 06C56A89949D617xxxxxxxxxxx
      keyPath: /var/weixin_cert/h5.p12
      notifyUrl: http://sell.natapp4.cc/sell/pay/notify
      templateId:
        orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ
    
    projectUrl:
      wechatMpAuthorize: http://sell.natapp4.cc
      wechatOpenAuthorize: http://sell.natapp4.cc
      sell: http://sell.natapp4.cc
    logging:
      level:
        com.imooc.dataobject.mapper: trace
    mybatis:
      mapper-locations: classpath:mapper/*.xml
    

    生产配置文件

    application-prod.yml

    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: 123456
        url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false
      jackson:
        default-property-inclusion: non_null
      redis:
        host: 192.168.30.113
        port: 6379
    server:
      context-path: /sell
    #logging:
    #  pattern:
    #    console: "%d - %msg%n"
    ##  path: /var/log/tomcat/
    #  file: /var/log/tomcat/sell.log
    #  level:
    #    com.imooc.LoggerTest: debug
    
    wechat:
      mpAppId: wxd898fcb01713c658
      mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx
      openAppId: wx6ad144e54af67d87
      openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx
      mchId: 1483469312
      mchKey: 06C56A89949D617xxxxxxxxxxx
      keyPath: /var/weixin_cert/h5.p12
      notifyUrl: http://sell.natapp4.cc/sell/pay/notify
      templateId:
        orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ
    
    projectUrl:
      wechatMpAuthorize: http://sell.natapp4.cc
      wechatOpenAuthorize: http://sell.natapp4.cc
      sell: http://sell.natapp4.cc
    logging:
      level:
        com.imooc.dataobject.mapper: trace
    mybatis:
      mapper-locations: classpath:mapper/*.xml
    

    配置文件类

    BlogProperties.java

    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.java

    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.java

    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.java

    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

    <?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依赖

    <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配置类

    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注解

    package com.mindex.controller;
    
    import com.mindex.entities.User;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.*;
    
    @RestController
    @RequestMapping(value = "/users")
    @Api(value = "/users", tags = "测试接口模块")
    public class UserController {
    
        //创建线程安全的Map
        static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());
    
        @ApiOperation(value = "获取用户列表", notes = "")
        @RequestMapping(value = "/", method = RequestMethod.GET)
        public List<User> getUserList() {
            // 处理"/users/"的GET请求,用来获取用户列表
            // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
            List<User> userList = new ArrayList<User>(users.values());
            return userList;
        }
    
        @ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
        @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
        @RequestMapping(value = "/", method = RequestMethod.POST)
        public String postUser(@ModelAttribute User user) {
            // 处理"/users/"的POST请求,用来创建User
            // 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数
            users.put(user.getId(), user);
            return "success";
        }
    
    
        @ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息")
        @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
        @RequestMapping(value = "/{id}", method = RequestMethod.GET)
        public User getUser(@PathVariable Long id) {
            // 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
            // url中的id可通过@PathVariable绑定到函数的参数中
            return users.get(id);
        }
    
        @ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
        @ApiImplicitParams({
                @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"),
                @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
        })
        @RequestMapping(value = "/{id}", method = RequestMethod.POST)
        public String putUser(@PathVariable Long id, @ModelAttribute User user) {
            // 处理"/users/{id}"的PUT请求,用来更新User信息
            User u = users.get(id);
            u.setName(user.getName());
            u.setAge(user.getAge());
            users.put(id, u);
            return "success";
        }
    
        @ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象")
        @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
        @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
        public String deleteUser(@PathVariable Long id) {
            // 处理"/users/{id}"的DELETE请求,用来删除User
            users.remove(id);
            return "success";
        }
    }
    

    启动tomcat查看文档

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


    IDEA插件

    JRebel插件

    引入POM依赖

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <version>1.5.1.RELEASE</version>
                <scope>provided</scope>
    </dependency>
    

    配置Application.java

    @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依赖

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

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

    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关键字输出,支持参数引用。

    package com.imooc;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = LoggerTest.class)
    @Slf4j
    public class LoggerTest {
    
        //    无需再写LoggerFactory
        //    private final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
    
        @Test
        public void test1() {
            String name = "imooc";
            String password = "12345";
            log.debug("debug...");
            log.info("name: {}, password: {}", name, password);
            log.error("error...");
            log.warn("warning...");
        }
    }
    

    项目运行类(主入口)

    SellApplication.java

    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.java

    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, "订单不存在"),
        ;
    
        private Integer code;
        private String message;
    
        ResultEnum(Integer code, String message) {
            this.code = code;
            this.message = message;
        }
    }
    

    util工具类

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

    构造结果VO视图

    ResultVOUtil.java

    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.java

    package com.imooc.utils;
    
    import java.util.Random;
    
    public class KeyUtil {
        /**
         * 生成唯一的主键
         * 格式: 时间+随机数
         *
         * @return
         */
        public static synchronized String genUniqueKey() {
            Random random = new Random();
    
            Integer number = random.nextInt(900000) + 100000;
            return System.currentTimeMillis() + String.valueOf(number);
        }
    }
    

    object -> json

    JsonUtil.java

    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.java

    package com.imooc.utils;
    
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.HashMap;
    import java.util.Map;
    
    public class CookieUtil {
    
        /**
         * 设置
         * @param response
         * @param name
         * @param value
         * @param maxAge
         */
        public static void set(HttpServletResponse response,
                               String name,
                               String value,
                               int maxAge) {
            Cookie cookie = new Cookie(name, value);
            cookie.setPath("/");
            cookie.setMaxAge(maxAge);
            response.addCookie(cookie);
        }
    
        /**
         * 获取cookie
         * @param request
         * @param name
         * @return
         */
        public static Cookie get(HttpServletRequest request,
                               String name) {
            Map<String, Cookie> cookieMap = readCookieMap(request);
            if (cookieMap.containsKey(name)) {
                return cookieMap.get(name);
            }else {
                return null;
            }
        }
    
        /**
         * 将cookie封装成Map
         * @param request
         * @return
         */
        private static Map<String, Cookie> readCookieMap(HttpServletRequest request) {
            Map<String, Cookie> cookieMap = new HashMap<>();
            Cookie[] cookies = request.getCookies();
            if (cookies != null) {
                for (Cookie cookie: cookies) {
                    cookieMap.put(cookie.getName(), cookie);
                }
            }
            return cookieMap;
        }
    }
    

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

    MathUtil.java

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

    VO视图层

    要返回的数据格式如下:

    第一层VO

    ResultVO.java

    package com.imooc.VO;
    
    import lombok.Data;
    
    /**
     * http请求返回的最外层对象
     */
    @Data
    public class ResultVO<T> {
    
        /* 状态码 */
        private Integer code;
    
        /* 提示信息 */
        private String msg;
    
        /* 具体内容 */
        private T data;
    }
    

    第二层VO

    ProductVO.java

    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.java

    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.java

    package com.imooc.dto;
    
    import com.imooc.dataobject.OrderDetail;
    import lombok.Data;
    import java.math.BigDecimal;
    import java.util.Date;
    import java.util.List;
    
    @Data
    public class OrderDTO {
        List<OrderDetail> orderDetailList;
        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;
    }
    

    CartDTO.java

    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;
        }
    }
    

    Exception异常处理

    自定义异常

    SellException.java

    package com.imooc.exception;
    
    import com.imooc.enums.ResultEnum;
    
    public class SellException extends RuntimeException {
    
        private Integer code;
    
        public SellException(ResultEnum resultEnum) {
            super(resultEnum.getMessage());
            this.code = resultEnum.getCode();
        }
    
        public SellException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    }
    

    ResponseBankException.java

    package com.imooc.exception;
    
    public class ResponseBankException extends RuntimeException {
    }
    

    SellerAuthorizeException.java

    package com.imooc.exception;
    
    public class SellerAuthorizeException extends RuntimeException {
    }
    

    自定义异常处理器

    SellExceptionHandler.java

    package com.imooc.handler;
    
    import com.imooc.VO.ResultVO;
    import com.imooc.config.ProjectUrlConfig;
    import com.imooc.exception.ResponseBankException;
    import com.imooc.exception.SellException;
    import com.imooc.exception.SellerAuthorizeException;
    import com.imooc.utils.ResultVOUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.servlet.ModelAndView;
    
    @ControllerAdvice
    public class SellExceptionHandler {
    
        @Autowired
        private ProjectUrlConfig projectUrlConfig;
    
        //拦截登录异常
        //http://sell.natapp4.cc/sell/wechat/qrAuthorize?returnUrl=http://sell.natapp4.cc/sell/seller/login
        @ExceptionHandler(value = SellerAuthorizeException.class)
        @ResponseStatus(HttpStatus.FORBIDDEN)
        public ModelAndView handlerAuthorizeException() {
            return new ModelAndView("redirect:"
            .concat(projectUrlConfig.getWechatOpenAuthorize())
            .concat("/sell/wechat/qrAuthorize")
            .concat("?returnUrl=")
            .concat(projectUrlConfig.getSell())
            .concat("/sell/seller/login"));
        }
    
        @ExceptionHandler(value = SellException.class)
        @ResponseBody
        public ResultVO handlerSellerException(SellException e) {
            return ResultVOUtil.error(e.getCode(), e.getMessage());
        }
    
        @ExceptionHandler(value = ResponseBankException.class)
        @ResponseStatus(HttpStatus.FORBIDDEN)
        public void handleResponseBankException() {
    
        }
    }
    

    统一异常处理

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

    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中。

    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输出。

    <!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:请求返回的数据。

    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。

    package com.mindex.exception;
    
    public class MyException extends Exception {
    
        public MyException(String message) {
            super(message);
        }
    }
    

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

    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异常创建对应的处理。

    @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.java

    package com.imooc.aspect;
    
    import com.imooc.constant.CookieConstant;
    import com.imooc.constant.RedisConstant;
    import com.imooc.exception.SellerAuthorizeException;
    import com.imooc.utils.CookieUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    
    @Aspect
    @Component
    @Slf4j
    public class SellerAuthorizeAspect {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        @Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
                "&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
        public void verify() {
        }
    
        @Before("verify()")
        public void doVerify() {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            //查询cookie
            Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
            if (cookie == null) {
                log.warn("【登录校验】Cookie中查不到token");
                throw new SellerAuthorizeException();
            }
    
            //去redis里查询
            String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
            if (StringUtils.isEmpty(tokenValue)) {
                log.warn("【登录校验】Redis中查不到token");
                throw new SellerAuthorizeException();
            }
        }
    }
    

    Data Object层(Entity)

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

    dataobject定义

    ProductCategory.java

    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() {
        }
    }
    

    Repository层

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

    repository定义

    ProductCategoryRepository.java

    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.java

    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 org.springframework.transaction.annotation.Transactional;
    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.findOne(1);
            System.out.println(productCategory.toString());
        }
    
        @Test
        @Transactional
        public void saveTest() {
            ProductCategory productCategory = new ProductCategory("男生最爱", 4);
            ProductCategory result = repository.save(productCategory);
            Assert.assertNotNull(result);
    //        Assert.assertNotEquals(null, result);
        }
    
        @Test
        public void findByCategoryTypeInTest() {
            List<Integer> list = Arrays.asList(2,3,4);
    
            List<ProductCategory> result = repository.findByCategoryTypeIn(list);
            Assert.assertNotEquals(0, result.size());
        }
    
        @Test
        public void updateTest() {
    //        ProductCategory productCategory = repository.findOne(4);
    //        productCategory.setCategoryName("男生最爱1");
            ProductCategory productCategory = new ProductCategory("男生最爱", 4);
            ProductCategory result = repository.save(productCategory);
            Assert.assertEquals(productCategory, result);
        }
    }
    

    Service层

    service接口

    CategoryService.java

    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实现

    需要使用@Service注解

    CategoryServiceImpl.java

    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);
        }
    }
    

    service单元测试

    CategoryServiceImplTest.java

    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;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class CategoryServiceImplTest {
    
        @Autowired
        private CategoryServiceImpl categoryService;
    
        @Test
        public void findOne() throws Exception {
            ProductCategory productCategory = categoryService.findOne(1);
            Assert.assertEquals(new Integer(1), productCategory.getCategoryId());
        }
    
        @Test
        public void findAll() throws Exception {
            List<ProductCategory> productCategoryList = categoryService.findAll();
            Assert.assertNotEquals(0, productCategoryList.size());
        }
    
        @Test
        public void findByCategoryTypeIn() throws Exception {
            List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(Arrays.asList(1,2,3,4));
            Assert.assertNotEquals(0, productCategoryList.size());
        }
    
        @Test
        public void save() throws Exception {
            ProductCategory productCategory = new ProductCategory("男生专享", 10);
            ProductCategory result = categoryService.save(productCategory);
            Assert.assertNotNull(result);
        }
    
    }
    

    Controller层

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

    BuyerProductController.java

    package com.imooc.controller;
    
    import com.imooc.VO.ProductInfoVO;
    import com.imooc.VO.ProductVO;
    import com.imooc.VO.ResultVO;
    import com.imooc.dataobject.ProductCategory;
    import com.imooc.dataobject.ProductInfo;
    import com.imooc.service.CategoryService;
    import com.imooc.service.ProductService;
    import com.imooc.utils.ResultVOUtil;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    
    @RestController
    @RequestMapping("/buyer/product")
    public class BuyerProductController {
    
        @Autowired
        private ProductService productService;
    
        @Autowired
        private CategoryService categoryService;
    
        @GetMapping("/list")
        public ResultVO list() {
            //1. 查询所有上架商品
            List<ProductInfo> productInfoList = productService.findUpAll();
    
            //2. 查询类目(一次性查询)
            //传统方法
            List<Integer> categoryTypeList = new ArrayList<>();
    //        for (ProductInfo productInfo : productInfoList) {
    //            categoryTypeList.add(productInfo.getCategoryType());
    //        }
    
            //精简方法(java8, lambda)
            categoryTypeList = productInfoList.stream().map(e -> e.getCategoryType()).collect(Collectors.toList());
            List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList);
    
            //3. 数据拼装
            List<ProductVO> productVOList = new ArrayList<>();
            for (ProductCategory productCategory : productCategoryList) {
                ProductVO productVO = new ProductVO();
                productVO.setCategoryName(productCategory.getCategoryName());
                productVO.setCategoryType(productCategory.getCategoryType());
    
                List<ProductInfoVO> productInfoVOList = new ArrayList<>();
                for (ProductInfo productInfo : productInfoList) {
                    if (productInfo.getCategoryType().equals(productCategory.getCategoryType())) {
                        ProductInfoVO productInfoVO = new ProductInfoVO();
                        BeanUtils.copyProperties(productInfo, productInfoVO);
                        productInfoVOList.add(productInfoVO);
                    }
                }
                productVO.setProductInfoVOList(productInfoVOList);
                productVOList.add(productVO);
            }
    
            return ResultVOUtil.success(productVOList);
        }
    }
    

    SellerProductController.java

    package com.imooc.controller;
    
    import com.imooc.dataobject.ProductCategory;
    import com.imooc.dataobject.ProductInfo;
    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.cache.annotation.CacheEvict;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.StringUtils;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.servlet.ModelAndView;
    import javax.validation.Valid;
    import java.util.List;
    import java.util.Map;
    
    @Controller
    @RequestMapping("/seller/product")
    public class SellerProductController {
    
        @Autowired
        private ProductService productService;
    
        @Autowired
        private CategoryService categoryService;
    
        /**
         * 列表
         * @param page
         * @param size
         * @param map
         * @return
         */
        @GetMapping("/list")
        public ModelAndView list(@RequestParam(value = "page", defaultValue = "1") Integer page,
                                 @RequestParam(value = "size", defaultValue = "10") Integer size,
                                 Map<String, Object> map) {
            PageRequest request = new PageRequest(page - 1, size);
            Page<ProductInfo> productInfoPage = productService.findAll(request);
            map.put("productInfoPage", productInfoPage);
            map.put("currentPage", page);
            map.put("size", size);
            return new ModelAndView("product/list", map);
        }
    
        /**
         * 商品上架
         * @param productId
         * @param map
         * @return
         */
        @RequestMapping("/on_sale")
        public ModelAndView onSale(@RequestParam("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);
        }
        /**
         * 商品下架
         * @param productId
         * @param map
         * @return
         */
        @RequestMapping("/off_sale")
        public ModelAndView offSale(@RequestParam("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);
        }
    
        @GetMapping("/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);
        }
    
        /**
         * 保存/更新
         * @param form
         * @param bindingResult
         * @param map
         * @return
         */
        @PostMapping("/save")
    //    @Cacheable(cacheNames = "product", key = "123")
    //    @Cacheable(cacheNames = "product", key = "456")
    //    @CachePut(cacheNames = "product", key = "123")
        @CacheEvict(cacheNames = "product", allEntries = true, beforeInvocation = true)
        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 {
                //如果productId为空, 说明是新增
                if (!StringUtils.isEmpty(form.getProductId())) {
                    productInfo = productService.findOne(form.getProductId());
                } else {
                    form.setProductId(KeyUtil.genUniqueKey());
                }
                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);
        }
    }
    

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

    mapper层

    ProductCategoryMapper.java

    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.java

    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.java

    package com.imooc.dataobject.dao;
    
    import com.imooc.dataobject.mapper.ProductCategoryMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import java.util.Map;
    
    public class ProductCategoryDao {
    
        @Autowired
        ProductCategoryMapper mapper;
    
        public int insertByMap(Map<String, Object> map) {
            return mapper.insertByMap(map);
        }
    }
    

    对象转换

    Form表单对象(Json)转成DTO

    OrderForm2OrderDTOConverter.java

    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.java

    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

    spring.http.encoding.force=true
    spring.http.encoding.charset=UTF-8
    spring.http.encoding.enabled=true
    server.tomcat.uri-encoding=UTF-8
    

    IDEA设置

    网页模板

    Thymeleaf

    pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
    ...
    
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
    

    index.html

    <!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

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

    ThymeleafTest.java

    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";
        }
    }
    

    运行结果如下:


    数据库操作

    JdbcTemplate

    引入POM依赖

    <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

    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

    package com.mindex.service;
    
    public interface UserService {
    
        void create(String name, Integer age);
        
        void deleteByName(String name);
        
        Integer getAllUsers();
        
        void deleteAllUsers();
    }
    

    Service实现:UserServiceImpl.java

    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

    package com.mindex.service.impl;
    
    import com.mindex.service.UserService;
    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest
    public class UserServiceImplTest {
    
        @Autowired
        private UserService userService;
    
        @Test
        public void create() throws Exception {
            // 插入5个用户
            userService.create("a", 1);
            userService.create("b", 2);
            userService.create("c", 3);
            userService.create("d", 4);
            userService.create("e", 5);
    
            Assert.assertEquals(5, userService.getAllUsers().intValue());
        }
    
        @Test
        public void deleteByName() throws Exception {
            userService.deleteByName("b");
            userService.deleteByName("c");
    
            Assert.assertEquals(3, userService.getAllUsers().intValue());
        }
    
        @Test
        public void getAllUsers() throws Exception {
        }
    
        @Test
        public void deleteAllUsers() throws Exception {
        }
    }
    

    Spring-data-jpa

    引入pom依赖:pom.xml

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

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

    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时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

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

    User.java

    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() {
        }
    }
    

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

    package com.mindex.repository;
    
    import com.mindex.entities.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.data.repository.query.Param;
    
    
    public interface UserRepository extends JpaRepository<User, Long> {
    
        User findByName(String name);
    
        User findByNameAndAge(String name, Integer age);
    
        @Query("from User u where u.name=:name")
        User findUser(@Param("name") String name);
    
    }
    

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

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

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

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

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

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

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

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

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

    package com.mindex.repository;
    
    import com.mindex.entities.User;
    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @SpringBootTest
    @RunWith(SpringJUnit4ClassRunner.class)
    public class UserRepositoryTest {
    
        @Autowired
        private UserRepository userRepository;
    
        @Test
        public void test() throws Exception {
            // 创建10条记录
            userRepository.save(new User("AAA", 10));
            userRepository.save(new User("BBB", 20));
            userRepository.save(new User("CCC", 30));
            userRepository.save(new User("DDD", 40));
            userRepository.save(new User("EEE", 50));
            userRepository.save(new User("FFF", 60));
            userRepository.save(new User("GGG", 70));
            userRepository.save(new User("HHH", 80));
            userRepository.save(new User("III", 90));
            userRepository.save(new User("JJJ", 100));
    
            // 测试findAll, 查询所有记录
            Assert.assertEquals(10, userRepository.findAll().size());
    
            // 测试findByName, 查询姓名为FFF的User
            Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());
    
            // 测试findUser, 查询姓名为FFF的User
            Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());
    
            // 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
            Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());
    
            // 测试删除姓名为AAA的User
            userRepository.delete(userRepository.findByName("AAA"));
    
            // 测试findAll, 查询所有记录, 验证上面的删除是否成功
            Assert.assertEquals(9, userRepository.findAll().size());
        }
    
    }
    

    集成Redis

    手动配置集成Redis

    引入POM依赖:pom.xml

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

    外部配置文件:application.yaml

    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

    package com.mindex.config;
    
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    @Data
    @Component
    public class RedisConfig {
    
        @Bean("jedis.config")
        public JedisPoolConfig jedisPoolConfig(@Value("${jedis.pool.min-idle}") int minIdle,
                                               @Value("${jedis.pool.max-idle}") int maxIdle,
                                               @Value("${jedis.pool.max-wait}") int maxWaitMillis,
                                               @Value("${jedis.pool.block-when-exhausted}") boolean blockWhenExhausted,
                                               @Value("${jedis.pool.max-total}") int maxTotal) {
    
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMinIdle(minIdle);
            config.setMaxIdle(maxIdle);
            config.setMaxWaitMillis(maxWaitMillis);
            config.setMaxTotal(maxTotal);
            // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
            config.setBlockWhenExhausted(blockWhenExhausted);
            // 是否启用pool的jmx管理功能, 默认true
            config.setJmxEnabled(true);
            return config;
        }
    
        @Bean
        public JedisPool jedisPool(@Qualifier("jedis.config") JedisPoolConfig config,
                                   @Value("${jedis.host}") String host,
                                   @Value("${jedis.port}") int port) {
            return new JedisPool(config, host, port);
        }
    }
    

    Service接口定义:RedisService.java

    package com.mindex.service;
    
    public interface RedisService {
    
        String get(String key);
    
        boolean set(String key, String val);
    
    }
    

    Service接口实现类:RedisServiceImpl.java

    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

    package com.mindex.service.impl;
    
    import com.mindex.service.RedisService;
    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class RedisServiceImplTest {
    
        @Autowired
        private RedisService redisService;
    
        @Test
        public void testGet() {
            // test set
            boolean status = this.redisService.set("foo", "bar");
            Assert.assertTrue(status);
    
            // test get
            String str = this.redisService.get("foo");
            Assert.assertEquals("bar", str);
        }
    }
    

    在Redis中检查结果


    使用spring-boot-starter-data-redis

    外部配置文件:application.yaml

    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

    package com.mindex.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.*;
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration
    public class RedisConfig1 {
    
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
    
        /**
         * 实例化 RedisTemplate 对象
         *
         */
        @Bean
        public RedisTemplate<String, Object> functionDomainRedisTemplate() {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            this.initRedisTemplate(redisTemplate, redisConnectionFactory);
            return redisTemplate;
        }
    
        /**
         * 序列化设置
         */
        private void initRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
            redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
            redisTemplate.setConnectionFactory(factory);
        }
    
        @Bean
        public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForHash();
        }
    
        @Bean
        public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForValue();
        }
    
        @Bean
        public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForList();
        }
    
        @Bean
        public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForSet();
        }
    
        @Bean
        public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForZSet();
        }
    
    }
    

    简单测试:RedisAnotherConfigTest.java

    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依赖:

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

    增加配置:application.properties

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

    spring.mail.host=smtp.qq.com
    spring.mail.username=85648606@qq.com
    spring.mail.password=clqpsraiifwqbidg
    spring.mail.properties.mail.smtp.auth=true
    spring.mail.properties.mail.smtp.starttls.enable=true
    spring.mail.properties.mail.smtp.starttls.required=true
    

    单元测试:MailTest.java

    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格式邮件

    @Test
        public void sendAttachmentsMail() throws Exception {
    
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage);
            mimeMessageHelper.setFrom("85648606@qq.com");
            mimeMessageHelper.setTo("wfgdlut@msn.com");
            mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【HTML】");
    
            StringBuilder sb = new StringBuilder();
            sb.append("<html><head></head>");
            sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>");
            sb.append("</html>");
    
            // 启用html
            mimeMessageHelper.setText(sb.toString(), true);
            // 发送邮件
            mailSender.send(mimeMessage);
    
            System.out.println("邮件已发送");
    
        }
    

    发送包含内嵌图片的邮件

    /**
         * 发送包含内嵌图片的邮件
         *
         * @throws Exception
         */
        @Test
        public void sendAttachedImageMail() throws Exception {
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            // multipart模式
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            mimeMessageHelper.setFrom("85648606@qq.com");
            mimeMessageHelper.setTo("wfgdlut@msn.com");
            mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【图片】");
    
            StringBuilder sb = new StringBuilder();
            sb.append("<html><head></head>");
            sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p>");
            // cid为固定写法,imageId指定一个标识
            sb.append("<img src=\"cid:imageId\"/></body>");
            sb.append("</html>");
    
            // 启用html
            mimeMessageHelper.setText(sb.toString(), true);
    
            // 设置imageId
            FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png"));
            mimeMessageHelper.addInline("imageId", img);
    
            // 发送邮件
            mailSender.send(mimeMessage);
    
            System.out.println("邮件已发送");
        }
    

    发送包含附件的邮件

    /**
         * 发送包含附件的邮件
         * @throws Exception
         */
        @Test
        public void sendAttendedFileMail() throws Exception {
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            // multipart模式
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8");
            mimeMessageHelper.setFrom("85648606@qq.com");
            mimeMessageHelper.setTo("wfgdlut@msn.com");
            mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【附件】");
    
            StringBuilder sb = new StringBuilder();
            sb.append("<html><head></head>");
            sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>");
            sb.append("</html>");
    
            // 启用html
            mimeMessageHelper.setText(sb.toString(), true);
            // 设置附件
            FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png"));
            mimeMessageHelper.addAttachment("test.png", img);
    
            // 发送邮件
            mailSender.send(mimeMessage);
    
            System.out.println("邮件已发送");
        }
    

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/bwmcrftx.html