Spring Boot 學習筆記 - 鋼鋼更新

背景介紹

該文檔是在慕課網實戰課程《Spring Boot企業微信點餐系統》基礎上總結而成,旨在記錄Spring Boot一些相關知識,文章中涉及的代碼都通過驗證,能夠直接使用。 該文檔做爲我的參考資料,會長期更新。php

慕課網課程地址:Spring Boot企業微信點餐系統 html

數據庫設計

微信點餐數據庫 - SQL.mdjava

-- 類目
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.xmlmysql

<?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.ymlgit

spring:
 profiles:
 active: dev
複製代碼

開發配置文件

application-dev.ymlgithub

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

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

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

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

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("郵件已發送");
    }
複製代碼
相關文章
相關標籤/搜索