常见代码块
in Projects with 0 comment

常见代码块

in Projects with 0 comment

数据库配置appication.properties

# 连接数据库的配置信息
spring.datasource.url=jdbc:mysql://localhost:3306/mall_pms?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# 激活Profile配置
spring.profiles.active=dev
# 连接数据库的固定配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# Mybatis的XML文件位置
mybatis.mapper-locations=classpath:mapper/*.xml


工具类

ResponseCode


/**
 * 错误代码枚举类型
 */
public enum ResponseCode {

    OK(200),
    BAD_REQUEST(400),
    UNAUTHORIZED(401),
    FORBIDDEN(403),
    NOT_FOUND(404),
    NOT_ACCEPTABLE(406),
    CONFLICT(409),
    INTERNAL_SERVER_ERROR(500);

    private Integer value;

    ResponseCode(Integer value) {
        this.value = value;
    }

    public Integer getValue() {
        return value;
    }

}

JsonResult

package cn.tedu.csmall.commons.restful;


import cn.tedu.csmall.commons.exception.CoolSharkServiceException;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

/**
 * 通用响应对象
 */
@Data
public class JsonResult<T> implements Serializable {

    /**
     * 状态码
     */
    @ApiModelProperty(value = "业务状态码", position = 1, example = "200, 400, 401, 403, 404, 409, 500")
    private Integer state;
    /**
     * 消息
     */
    @ApiModelProperty(value = "业务消息", position = 2, example = "登录失败!密码错误!")
    private String message;
    /**
     * 数据
     */
    @ApiModelProperty(value = "业务数据", position = 3)
    private T data;

    /**
     * 创建响应结果对象,表示"成功",不封装其它任何数据
     * @return 响应结果对象
     */
    public static JsonResult<Void> ok() {
        return ok("OK");
    }

    public static JsonResult ok(String message){
        JsonResult jsonResult=new JsonResult();
        jsonResult.setState(ResponseCode.OK.getValue());
        jsonResult.setMessage(message);
        jsonResult.setData(null);
        return jsonResult;
    }
    /**
     * 创建响应结果对象,表示"成功",且封装客户端期望响应的数据
     * @param data 客户端期望响应的数据
     * @return 响应结果对象
     */
    public static <T> JsonResult<T> ok(String message,T data) {
        JsonResult<T> jsonResult = new JsonResult<>();
        jsonResult.setState(ResponseCode.OK.getValue());
        jsonResult.setData(data);
        return jsonResult;
    }
    /**
     * 创建响应结果对象,表示"失败",且封装"失败"的描述
     *
     * @param e CoolSharkServiceException异常对象
     * @return 响应结果对象
     */
    public static JsonResult<Void> failed(CoolSharkServiceException e) {
        return failed(e.getResponseCode(), e);
    }

    /**
     * 创建响应结果对象,表示"失败",且封装"失败"的描述
     *
     * @param responseCode "失败"的状态码
     * @param e            "失败"时抛出的异常对象
     * @return 响应结果对象
     */
    public static JsonResult<Void> failed(ResponseCode responseCode, Throwable e) {
        return failed(responseCode, e.getMessage());
    }

    /**
     * 创建响应结果对象,表示"失败",且封装"失败"的描述
     *
     * @param responseCode "失败"的状态码
     * @param message      "失败"的描述文本
     * @return 响应结果对象
     */
    public static JsonResult<Void> failed(ResponseCode responseCode, String message) {
        JsonResult<Void> jsonResult = new JsonResult<>();
        jsonResult.setState(responseCode.getValue());
        jsonResult.setMessage(message);
        return jsonResult;
    }

}


entity类

@Data
public class Cart implements Serializable {
    private Integer id;
    // 商品编号
    private String commodityCode;
    // 价格
    private Integer price;
    // 数量
    private Integer count;
    // 用户id
    private Integer userId;

}

vo类


@Data
public class AdminLoginVO implements Serializable {

    private Long id;
    private String username;
    private String password;
    private Integer isEnable;
    private List<String> permissions;

}

dto类

@ApiModel("新增订单的DTO")
@Data
public class OrderAddDTO implements Serializable {
    @ApiModelProperty(value = "用户id",name="userId",example = "UU100")
    private String userId;
    @ApiModelProperty(value = "商品编号",name="commodityCode",example = "PC100")
    private String commodityCode;
    @ApiModelProperty(value = "商品数量",name="count",example = "5")
    private Integer count;
    @ApiModelProperty(value = "总金额",name="money",example = "50")
    private Integer money;
}

GlobalControllerExceptionHandler类



/**
 * 全局异常处理器
 */
@RestControllerAdvice
@Slf4j
public class GlobalControllerExceptionHandler {

    /**
     * 处理业务异常
     */
    @ExceptionHandler({CoolSharkServiceException.class})
    public JsonResult<Void> handleCoolSharkServiceException(CoolSharkServiceException e) {
      //CoolSharkServiceException是自定义异常
        log.debug("出现业务异常,业务错误码={},描述文本={}", e.getResponseCode().getValue(), e.getMessage());
        e.printStackTrace();
        JsonResult<Void> result = JsonResult.failed(e);
        log.debug("即将返回:{}", result);
        return result;
    }

    /**
     * 处理绑定异常(通过Validation框架验证请求参数时的异常)
     */
    @ExceptionHandler(BindException.class)
    public JsonResult<Void> handleBindException(BindException e) {
        log.debug("验证请求数据时出现异常:{}", e.getClass().getName());
        e.printStackTrace();
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        JsonResult<Void> result = JsonResult.failed(ResponseCode.BAD_REQUEST, message);
        log.debug("即将返回:{}", result);
        return result;
    }

    /**
     * 处理系统(其它)异常
     */
    @ExceptionHandler({Throwable.class})
    public JsonResult<Void> handleSystemError(Throwable e) {
        log.debug("出现系统异常,异常类型={},描述文本={}", e.getClass().getName(), e.getMessage());
        e.printStackTrace();
        JsonResult<Void> result = JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR, e);
        log.debug("即将返回:{}", result);
        return result;
    }
}

自定义异常类

@Data
@EqualsAndHashCode(callSuper = false)
public class CoolSharkServiceException extends RuntimeException {

    private ResponseCode responseCode;

    public CoolSharkServiceException(ResponseCode responseCode, String message) {
        super(message);
        setResponseCode(responseCode);
    }

}

在启动类引用此异常全局配置类:

@SpringBootApplication
@Import({CsmallCommonConfiguration.class}) // 新增
public class CsmallProductWebapiApplication {

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

}

BeanUtil

public abstract class BeanUtil {

    public static Object copyProperties(Object source, Object target, String... ignoreProperties) {
        if (source == null) {
            return target;
        }
        BeanUtils.copyProperties(source, target, ignoreProperties);
        return target;
    }

    public static <T> List<T> copyList(List sources, Class<T> clazz) {
        return copyList(sources, clazz, null);
    }

    public static <T> List<T> copyList(List sources, Class<T> clazz, Callback<T> callback) {
        List<T> targetList = new ArrayList<>();
        if (sources != null) {
            try {
                for (Object source : sources) {
                    T target = clazz.newInstance();
                    copyProperties(source, target);
                    if (callback != null) {
                        callback.set(source, target);
                    }
                    targetList.add(target);
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return targetList;
    }

    public static Map<String, Object> toMap(Object bean, String... ignoreProperties) {
        Map<String, Object> map = new LinkedHashMap<>();
        List<String> ignoreList = new ArrayList<>(Arrays.asList(ignoreProperties));
        ignoreList.add("class");
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
        for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) {
            if (!ignoreList.contains(pd.getName()) && beanWrapper.isReadableProperty(pd.getName())) {
                Object propertyValue = beanWrapper.getPropertyValue(pd.getName());
                map.put(pd.getName(), propertyValue);
            }
        }
        return map;
    }

    public static <T> T toBean(Map<String, Object> map, Class<T> beanType) {
        BeanWrapper beanWrapper = new BeanWrapperImpl(beanType);
        map.forEach((key, value) -> {
            if (beanWrapper.isWritableProperty(key)) {
                beanWrapper.setPropertyValue(key, value);
            }
        });
        return (T) beanWrapper.getWrappedInstance();
    }

    public static interface Callback<T> {
        void set(Object source, T target);
    }

    //检查Pojo对象是否有null字段
    public static boolean checkPojoNullField(Object o, Class<?> clz) {
        try {
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                if (field.get(o) == null) {
                    return false;
                }
            }
            if (clz.getSuperclass() != Object.class) {
                return checkPojoNullField(o, clz.getSuperclass());
            }
            return true;
        } catch (IllegalAccessException e) {
            return false;
        }
    }
}

MD5Util

public class MD5Util {

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }

    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}

NumberUtil

public class NumberUtil {

    private NumberUtil() {
    }


    /**
     * 判断是否为11位电话号码
     *
     * @param phone
     * @return
     */
    public static boolean isPhone(String phone) {
        Pattern pattern = Pattern.compile("^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0-8])|(18[0-9]))\\d{8}$");
        Matcher matcher = pattern.matcher(phone);
        return matcher.matches();
    }

    /**
     * 生成指定长度的随机数
     *
     * @param length
     * @return
     */
    public static int genRandomNum(int length) {
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }

    /**
     * 生成订单流水号
     *
     * @return
     */
    public static String genOrderNo() {
        StringBuffer buffer = new StringBuffer(String.valueOf(System.currentTimeMillis()));
        int num = genRandomNum(4);
        buffer.append(num);
        return buffer.toString();
    }
}

JsonPage类

// 通用支持分页查询的结果对象类型
@Data
public class JsonPage<T> implements Serializable {

    // 按照实际需求,定义这个类中的属性
    @ApiModelProperty(value = "当前页码",name = "pageNum")
    private Integer pageNum;
    @ApiModelProperty(value = "每页条数",name = "pageSize")
    private Integer pageSize;
    @ApiModelProperty(value = "总条数",name = "totalCount")
    private Long totalCount;
    @ApiModelProperty(value = "总页数",name = "totalPages")
    private Integer totalPages;
    // 声明一个属性,来承载查询到的分页数据结果
    @ApiModelProperty(value = "分页数据",name = "list")
    private List<T> list;

    // 所有属性写完了,下面要编写将其他框架的分页结果转换成当前类对象的方法
    // SpringDataElasticsearch或PageHelper等具有分页功能的框架,均有类似PageInfo的对象
    // 我们可以分别编写方法,将它们转换成JsonPage对象,我们先只编写PageHelper的转换
    public static <T> JsonPage<T> restPage(PageInfo<T> pageInfo){
        // 下面开始将pageInfo对象的属性赋值给JsonPage对象
        JsonPage<T> result=new JsonPage<>();
        result.setPageNum(pageInfo.getPageNum());
        result.setPageSize(pageInfo.getPageSize());
        result.setTotalCount(pageInfo.getTotal());
        result.setTotalPages(pageInfo.getPages());
        result.setList(pageInfo.getList());
        // 返回赋值完毕的JsonPage对象
        return result;
    }


}

HTTPClient类

package com.atguigu.ggkt.common.utils;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * http请求客户端
 */
public class HttpClientUtils {
	private String url;
	private Map<String, String> param;
	private int statusCode;
	private String content;
	private String xmlParam;
	private boolean isHttps;

	public boolean isHttps() {
		return isHttps;
	}

	public void setHttps(boolean isHttps) {
		this.isHttps = isHttps;
	}

	public String getXmlParam() {
		return xmlParam;
	}

	public void setXmlParam(String xmlParam) {
		this.xmlParam = xmlParam;
	}

	public HttpClientUtils(String url, Map<String, String> param) {
		this.url = url;
		this.param = param;
	}

	public HttpClientUtils(String url) {
		this.url = url;
	}

	public void setParameter(Map<String, String> map) {
		param = map;
	}

	public void addParameter(String key, String value) {
		if (param == null)
			param = new HashMap<String, String>();
		param.put(key, value);
	}

	public void post() throws ClientProtocolException, IOException {
		HttpPost http = new HttpPost(url);
		setEntity(http);
		execute(http);
	}

	public void put() throws ClientProtocolException, IOException {
		HttpPut http = new HttpPut(url);
		setEntity(http);
		execute(http);
	}

	public void get() throws ClientProtocolException, IOException {
		if (param != null) {
			StringBuilder url = new StringBuilder(this.url);
			boolean isFirst = true;
			for (String key : param.keySet()) {
				if (isFirst) {
					url.append("?");
					isFirst = false;
				}else {
					url.append("&");
				}
				url.append(key).append("=").append(param.get(key));
			}
			this.url = url.toString();
		}
		HttpGet http = new HttpGet(url);
		execute(http);
	}

	/**
	 * set http post,put param
	 */
	private void setEntity(HttpEntityEnclosingRequestBase http) {
		if (param != null) {
			List<NameValuePair> nvps = new LinkedList<NameValuePair>();
			for (String key : param.keySet())
				nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
			http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
		}
		if (xmlParam != null) {
			http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
		}
	}

	private void execute(HttpUriRequest http) throws ClientProtocolException,
			IOException {
		CloseableHttpClient httpClient = null;
		try {
			if (isHttps) {
				SSLContext sslContext = new SSLContextBuilder()
						.loadTrustMaterial(null, new TrustStrategy() {
							// 信任所有
							public boolean isTrusted(X509Certificate[] chain,
									String authType)
									throws CertificateException {
								return true;
							}
						}).build();
				SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
						sslContext);
				httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
						.build();
			} else {
				httpClient = HttpClients.createDefault();
			}
			CloseableHttpResponse response = httpClient.execute(http);
			try {
				if (response != null) {
					if (response.getStatusLine() != null)
						statusCode = response.getStatusLine().getStatusCode();
					HttpEntity entity = response.getEntity();
					// 响应内容
					content = EntityUtils.toString(entity, Consts.UTF_8);
				}
			} finally {
				response.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			httpClient.close();
		}
	}

	public int getStatusCode() {
		return statusCode;
	}

	public String getContent() throws ParseException, IOException {
		return content;
	}

}

PDFUtils类

package com.resume.tools;


import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Map;

public class PDFUtil {
    /**
     * 将数据填入pdf模板
     *
     * @param in   pdf模板输入流
     * @param data 替换数据
     */
    public static ByteArrayOutputStream generate(InputStream in, Map data) throws Exception {
        PdfReader template = new PdfReader(in);
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            PdfStamper stamp = new PdfStamper(template, out);
//            获取pdf文件表单域
            AcroFields form = stamp.getAcroFields();
            //支持中文
            BaseFont font = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
            //迭代map,将map对应的值写入表单
            for (Object o : data.keySet()) {
                String key = (String) o;
                String value = (String) data.get(key);
                form.setFieldProperty(key, "textfont", font, null);
                form.setField(key, value, value);
            }
            stamp.setFormFlattening(true);
            stamp.close();
            return out;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        } finally {
            template.close();
            in.close();
        }
    }
}

yml文件配置

application.yml


server:
  port: 20000
#公共配置
mybatis:
  configuration:
    # 禁用缓存
    cache-enabled: false
    # 配置映射驼峰命名法,数据库中user_name的字段,会映射在java的userName属性上
    map-underscore-to-camel-case: true
    # 将运行的sql语句输出到控制台
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
knife4j:
  # 开启增强配置
  enable: true
  # 生产环境屏蔽,开启将禁止访问在线API文档
  production: false
  # Basic认证功能,即是否需要通过用户名、密码验证后才可以访问在线API文档
  basic:
    # 是否开启Basic认证
    enable: false
    # 用户名,如果开启Basic认证却未配置用户名与密码,默认是:admin/123321
    username: root
    # 密码
    password: root
spring:
  profiles:
    active: dev

application-dev.yml

spring:
  application:
    name: nacos-cart
     # 当前Springboot项目的名称,用作注册中心服务的名称
  cloud:
    nacos:
      discovery:
        server-addr: 106.75.107.221:8848
          # 定义nacos运行的路径
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
        port: 8722
  datasource:
    url: jdbc:mysql://106.75.107.221:3306/csmall_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    username: root
    password: tarena2017Up;
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
dubbo:
  application:
    name: nacos-cart
  protocol:
    port: -1
    name: dubbo
  registry:
    address: nacos://106.75.107.221:8848
  consumer:
    check: false
seata:
  tx-service-group: csmall_group
  service:
    vgroup-mapping:
      csmall_group: default
    grouplist:
      default: 106.75.107.221:8091

MybatisPlus配置yml文件

server:
  port: 8080
spring:
  profiles:
    active: dev
  application:
    name: my-springsecurity-plus
  datasource:
    driver:
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 后面时区不要忘了如果你是mysql8.0以上的版本
    url: jdbc:mysql://localhost:3306/my-springsecurity-plus?serverTimezone=Asia/Shanghai
    username: root
    password: 180430121
    type: com.alibaba.druid.pool.DruidDataSource #druid连接池之后会解释这里先复制
    druid:
      # 初始化配置
      initial-size: 3
      # 最小连接数
      min-idle: 3
      # 最大连接数
      max-active: 15
      # 获取连接超时时间
      max-wait: 5000
      # 连接有效性检测时间
      time-between-eviction-runs-millis: 90000
      # 最大空闲时间
      min-evictable-idle-time-millis: 1800000
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      validation-query: select 1
      # 配置监控统计拦截的filters
      filters: stat
      web-stat-filter:
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      #StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
      stat-view-servlet:
        enabled: true #是否启用StatViewServlet默认值true
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin
        login-password: admin
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

# mybatis配置
mybatis:
  type-aliases-package: com.codermy.myspringsecurityplus.entity
  mapper-locations: classpath:/mybatis-mappers/*
  configuration:
    map-underscore-to-camel-case: true

logging:
  file:
    path: src\main\resources\logger\ # logger文件夹需要提前生成



Druid配置yml文件

spring:
  profiles:
    active: dev
  application:
    name: my-springsecurity-plus
  datasource:
    driver:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/my-springsecurity-plus?serverTimezone=Asia/Shanghai
    username: root
    password: 180430121
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 初始化配置
      initial-size: 3
      # 最小连接数
      min-idle: 3
      # 最大连接数
      max-active: 15
      # 获取连接超时时间
      max-wait: 5000
      # 连接有效性检测时间
      time-between-eviction-runs-millis: 90000
      # 最大空闲时间
      min-evictable-idle-time-millis: 1800000
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false

      validation-query: select 1
      # 配置监控统计拦截的filters
      filters: stat
      web-stat-filter:
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      #StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
      stat-view-servlet:
        enabled: true #是否启用StatViewServlet默认值true
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin #用户名
        login-password: admin #密码

Configuration

配置cmmons工程

// 当前项目默认情况下不会扫描commons项目中的资源和内容,编写这个类,配置扫描commons
@Configuration // 所有配置Spring的配置类必须添加这个注解
@ComponentScan(basePackages = "cn.tedu.csmall.commons.exception")
public class CommonsConfiguration {
}

配置Swagger3

public class Swagger3Config {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.OAS_30)
                .apiInfo(apiInfo())
                .ignoredParameterTypes(MallUser.class, AdminUserToken.class)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.newbie.api"))
                .paths(PathSelectors.any())
                .build()
                .globalRequestParameters(getGlobalRequestParameters());
    }

    //生成全局通用参数
    private List<RequestParameter> getGlobalRequestParameters() {
        List<RequestParameter> parameters = new ArrayList<>();
        parameters.add(new RequestParameterBuilder()
                .name("token")
                .description("登录认证token")
                .required(false) // 非必传
                .in(ParameterType.HEADER) //请求头中的参数,其它类型可以点进ParameterType类中查看
                .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING)))
                .build());
        return parameters;
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("牛逼商城接口文档")
                .description("swagger接口文档")
                .version("2.0")
                .build();
    }
}

配置Knife4j

@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {

    /**
     * 【重要】指定Controller包路径
     */
    private String basePackage = "cn.tedu.csmall.business.controller";
    /**
     * 分组名称
     */
    private String groupName = "base-business";
    /**
     * 主机名
     */
    private String host = "http://java.tedu.cn";
    /**
     * 标题
     */
    private String title = "酷鲨商城项目案例在线API文档--基础business-web实例";
    /**
     * 简介
     */
    private String description = "构建基础business-web项目,实现购买";
    /**
     * 服务条款URL
     */
    private String termsOfServiceUrl = "http://www.apache.org/licenses/LICENSE-2.0";
    /**
     * 联系人
     */
    private String contactName = "项目研发部";
    /**
     * 联系网址
     */
    private String contactUrl = "http://java.tedu.cn";
    /**
     * 联系邮箱
     */
    private String contactEmail = "java@tedu.cn";
    /**
     * 版本号
     */
    private String version = "1.0-SNAPSHOT";

    @Autowired
    private OpenApiExtensionResolver openApiExtensionResolver;

    @Bean
    public Docket docket() {
        String groupName = "1.0-SNAPSHOT";
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .host(host)
                .apiInfo(apiInfo())
                .groupName(groupName)
                .select()
                .apis(RequestHandlerSelectors.basePackage(basePackage))
                .paths(PathSelectors.any())
                .build()
                .extensions(openApiExtensionResolver.buildExtensions(groupName));
        return docket;
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(title)
                .description(description)
                .termsOfServiceUrl(termsOfServiceUrl)
                .contact(new Contact(contactName, contactUrl, contactEmail))
                .version(version)
                .build();
    }

}

配置Mybatis



/**
 * 只做包扫描
 * 如果不需要mybatis配置可以注释掉
 */
@Configuration
@MapperScan("cn.tedu.csmall.cart.webapi.mapper")
public class MyBatisConfiguration {
}

配置Swagger2

@Configuration//表明这是一个配置类
@EnableSwagger2//开启Swagger
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")//组名称
                .apiInfo(webApiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.codermy.myspringsecurityplus.controller"))//扫描的包
                .paths(PathSelectors.any())
                .build();

    }
    /**
     * 该套 API 说明,包含作者、简介、版本、等信息
     * @return
     */
    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("my-springsecurity-plus-API文档")
                .description("本文档描述了my-springsecurity-plus接口定义")
                .version("1.0")
                .build();
    }

}

配置Security



@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 禁用防跨域攻击
        http.csrf().disable();

        // URL白名单
        String[] urls = {
                "/admins/login",
                "/doc.html",
                "/**/*.js",
                "/**/*.css",
                "/swagger-resources",
                "/v2/api-docs",
                "/favicon.ico"
        };

        // 配置各请求路径的认证与授权
        http.authorizeRequests() // 请求需要授权才可以访问
                .antMatchers(urls) // 匹配一些路径
                .permitAll() // 允许直接访问(不需要经过认证和授权)
                .anyRequest() // 匹配除了以上配置的其它请求
                .authenticated(); // 都需要认证

        // 注册处理JWT的过滤器
        // 此过滤器必须在Spring Security处理登录的过滤器之前
        // request ---> Filter ---> DispatcherServlet ---> Interceptor ---> Controller
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

SpringMVC配置跨域

csmall-product-webapi的根包下config包下创建SpringMvcConfiguration类,实现WebMvcConfigururer接口,重写其中的方法,以解决跨域问题:


@Configuration
public class SpringMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }

}

全局异常配置类

使用错误的测试数据时,会发现根本不会处理异常,是因为在启动类中默认执行的组件扫描不会扫描全局异常所在的包,为了解决此问题,应该先创建配置类,且配置组件扫描:


@Configuration
@ComponentScan("cn.tedu.csmall.common.ex.handler")
public class CsmallCommonConfiguration {
}

配置Redis

在基于Spring Boot的开发中,当需要在程序中访问Redis中的数据时,需要添加spring-boot-starter-data-redis依赖项。

要操作Redis中的数据,需要使用RedisTemplate对象,则在csmall-product-webapi的根包下的config包中创建RedisConfiguration类,并在其中进行配置:

@Configuration
public class RedisConfiguration {
    
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.json());
        return redisTemplate;
    }
    
}
    <result column="is_enable" property="isEnable" />
    <collection property="permissions" ofType="java.lang.String">
        <!-- 以下配置类似在Java中执行 new String("/pms/product/read") -->
        <constructor>
            <arg column="value" />
        </constructor>
    </collection>
</resultMap>


​
## 新增和修改
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mall.category.mapper.CategoryMapper">
​
<!--    int updateCategory(Category category);-->
​
​
    <update id="update" useGeneratedKeys="true" keyProperty="id">
        update pms_category set enable=0
        where id=#{id};
    </update>
​
    <!-- int insert(Category category); -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into pms_category (
            name, parent_id, depth, keywords, sort,
            icon, enable, is_parent, is_display, gmt_create,
            gmt_modified
        ) values (
                     #{name}, #{parentId}, #{depth}, #{keywords}, #{sort},
                     #{icon}, #{enable}, #{isParent}, #{isDisplay}, #{gmtCreate},
                     #{gmtModified}
                 )
    </insert>

</mapper>

测试代码块

测试持久层

测试连接数据库

配置完Mybatis之后, 可以尝试测试连接到数据库.则在src/test/java下找到默认即存在的测试类,编写并执行测试:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;

@SpringBootTest
class CsmallProductWebapiApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() {
    }
    
    @Test
    void testConnection() {
        Assertions.assertDoesNotThrow(() -> {
            dataSource.getConnection();
        });
    }

}

执行整个测试,应该能够通过测试。

测试新增

Mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.tedu.csmall.product.webapi.mapper.CategoryMapper">

    <!-- int insert(Category category); -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into pms_category (
            name, parent_id, depth, keywords, sort,
            icon, enable, is_parent, is_display, gmt_create,
            gmt_modified
        ) values (
            #{name}, #{parentId}, #{depth}, #{keywords}, #{sort},
            #{icon}, #{enable}, #{isParent}, #{isDisplay}, #{gmtCreate},
            #{gmtModified}
        )
    </insert>

</mapper>

测试
先在src/test下创建resources文件夹,并在此文件夹中创建truncate.sql脚本,此脚本将用于重置数据表:

truncate pms_category;

然后,在src/test/java的根包下创建mapper.CategoryMapperTests,编写并执行测试:

package cn.tedu.csmall.product.webapi.mapper;

import cn.tedu.csmall.pojo.entity.Category;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;

@SpringBootTest
public class CategoryMapperTests {

    @Autowired
    CategoryMapper mapper;

    @Test
    @Sql("classpath:truncate.sql")
    public void testInsert() {
        // 测试数据
        Category category = new Category();
        category.setName("手机");
        // 断言不会抛出异常
        assertDoesNotThrow(() -> {
            int rows = mapper.insert(category);
            assertEquals(1, rows);
            assertEquals(1, category.getId());
        });
    }

}

测试查询

配置SQL语句

csmall-product-webapiCategoryMapper.xml中添加配置:

<!-- CategorySimpleVO getByName(String name); -->
<select id="getByName" resultMap="SimpleResultMap">
    select id from pms_category where name=#{name}
</select>

<resultMap id="SimpleResultMap" type="cn.tedu.csmall.pojo.vo.CategorySimpleVO">
    <id column="id" property="id" />
</resultMap>

测试

csmall-product-webapisrc\test\resources下创建insert_data.sql文件,用于插入测试数据:

insert into pms_category (name) value ('类别001'), ('类别002');

然后,在CategoryMapperTests中添加测试方法:

@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetByNameSuccessfully() {
    // 测试数据
    String name = "类别001";
    // 断言不会抛出异常
    assertDoesNotThrow(() -> {
        // 执行查询
        CategorySimpleVO category = mapper.getByName(name);
        // 断言查询结果不为null
        assertNotNull(category);
    });
}

@Test
@Sql({"classpath:truncate.sql"})
public void testGetByNameFailBecauseNotFound() {
    // 测试数据
    String name = "类别999";
    // 断言不会抛出异常
    assertDoesNotThrow(() -> {
        // 执行查询
        CategorySimpleVO category = mapper.getByName(name);
        // 断言查询结果为null
        assertNull(category);
    });
}

业务逻辑层

src/test/java下的根包下创建service.CategoryServiceTests测试类,编写并执行测试:



@SpringBootTest
public class CategoryServiceTests {

    @Autowired
    ICategoryService service;

    @Test
    @Sql("classpath:truncate.sql")
    public void testAddNewSuccessfully() {
        // 测试数据
        CategoryAddNewDTO category = new CategoryAddNewDTO();
        category.setName("大屏智能手机");
        category.setParentId(0L);
        category.setIcon("未上传类别图标");
        category.setKeywords("未设置关键字");
        category.setSort(88);
        category.setIsDisplay(1);
        // 断言不会抛出异常
        assertDoesNotThrow(() -> {
            // 执行测试
            service.addNew(category);
        });
    }

    @Test
    @Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
    public void testAddNewFailBecauseNameDuplicate() {
        // 测试数据
        CategoryAddNewDTO category = new CategoryAddNewDTO();
        category.setName("类别001");
        // 断言不会抛出异常
        assertThrows(ServiceException.class, () -> {
            // 执行测试
            service.addNew(category);
        });
    }

    @Test
    @Sql({"classpath:truncate.sql"})
    public void testAddNewFailBecauseParentNotFound() {
        // 测试数据
        CategoryAddNewDTO category = new CategoryAddNewDTO();
        category.setName("类别001");
        category.setParentId(-1L);
        // 断言不会抛出异常
        assertThrows(ServiceException.class, () -> {
            // 执行测试
            service.addNew(category);
        });
    }

}

控制器层

@SpringBootTest
@AutoConfigureMockMvc
public class CategoryControllerTests {
    
    @Autowired
    MockMvc mockMvc;
    
    @Test
    @Sql("classpath:truncate.sql")
    public void testAddNewSuccessfully() throws Exception {
        // 准备测试数据,不需要封装,应该全部声明为String类型
        String name = "水果";
        String parentId = "0"; // 即使目标类型是Long,参数值也不要加L
        String keywords = "水果的关键字是啥";
        String sort = "66";
        String icon = "图标待定";
        String isDisplay = "1";
        // 请求路径,不需要写协议、服务器主机和端口号
        String url = "/categories/add-new";
        // 执行测试
        // 以下代码相对比较固定
        mockMvc.perform( // 执行发出请求
                MockMvcRequestBuilders.post(url) // 根据请求方式决定调用的方法
                .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
                .param("name", name) // 请求参数,有多个时,多次调用param()方法
                .param("parentId", parentId)
                .param("keywords", keywords)
                .param("icon", icon)
                .param("sort", sort)
            	.param("isDisplay", isDisplay)
                .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
                .andExpect( // 预判结果,类似断言
                        MockMvcResultMatchers
                                .jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
                                .value(20000)) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
                .andDo( // 需要执行某任务
                        MockMvcResultHandlers.print()); // 打印日志
    }
    
}

全局异常测试

@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testAddNewFailBecauseNameDuplicate() throws Exception {
    // 准备测试数据,不需要封装,应该全部声明为String类型
    String name = "类别001";
    String parentId = "0"; // 即使目标类型是Long,参数值也不要加L
    String keywords = "水果的关键字是啥";
    String sort = "66";
    String icon = "图标待定";
    String isDisplay = "1";
    // 请求路径,不需要写协议、服务器主机和端口号
    String url = "/categories/add-new";
    // 执行测试
    // 以下代码相对比较固定
    mockMvc.perform( // 执行发出请求
            MockMvcRequestBuilders.post(url) // 根据请求方式决定调用的方法
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
                    .param("name", name) // 请求参数,有多个时,多次调用param()方法
                    .param("parentId", parentId)
                    .param("keywords", keywords)
                    .param("icon", icon)
                    .param("sort", sort)
                    .param("isDisplay", isDisplay)
                    .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
            .andExpect( // 预判结果,类似断言
                    MockMvcResultMatchers
                            .jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
                            .value(State.ERR_CATEGORY_NAME_DUPLICATE.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
            .andDo( // 需要执行某任务
                    MockMvcResultHandlers.print()); // 打印日志
}

@Test
@Sql({"classpath:truncate.sql"})
public void testAddNewFailBecauseParentNotFound() throws Exception {
    // 准备测试数据,不需要封装,应该全部声明为String类型
    String name = "类别001";
    String parentId = "-1"; // 即使目标类型是Long,参数值也不要加L
    String keywords = "水果的关键字是啥";
    String sort = "66";
    String icon = "图标待定";
    String isDisplay = "1";
    // 请求路径,不需要写协议、服务器主机和端口号
    String url = "/categories/add-new";
    // 执行测试
    // 以下代码相对比较固定
    mockMvc.perform( // 执行发出请求
            MockMvcRequestBuilders.post(url) // 根据请求方式决定调用的方法
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
                    .param("name", name) // 请求参数,有多个时,多次调用param()方法
                    .param("parentId", parentId)
                    .param("keywords", keywords)
                    .param("icon", icon)
                    .param("sort", sort)
                    .param("isDisplay", isDisplay)
                    .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
            .andExpect( // 预判结果,类似断言
                    MockMvcResultMatchers
                            .jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
                            .value(State.ERR_CATEGORY_NOT_FOUND.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
            .andDo( // 需要执行某任务
                    MockMvcResultHandlers.print()); // 打印日志
}

测试Redis

在测试的根包下创建RedisTests来测试访问Redis中的数据:

@SpringBootTest
public class RedisTests {

    @Autowired
    RedisTemplate<String, Serializable> redisTemplate;

    @Test
    void testSetValue() {
        redisTemplate.opsForValue()
                .set("name", "liuguobin");
    }

    @Test
    void testSetValueTTL() {
        redisTemplate.opsForValue()
                .set("name", "fanchuanqi", 60, TimeUnit.SECONDS);
    }

    @Test
    void testSetObjectValue() {
        CategoryDetailsVO category = new CategoryDetailsVO();
        category.setId(65L);
        category.setIsParent(1);
        category.setDepth(1);
        category.setName("水果");
        redisTemplate.opsForValue()
                .set("category", category);
    }

    @Test
    void testGetValue() {
        // 当key存在时,可获取到有效值
        // 当key不存在时,获取到的结果将是null
        Serializable name = redisTemplate.opsForValue()
                .get("name");
        System.out.println("get value --> " + name);
    }

    @Test
    void testGetObjectValue() {
        // 当key存在时,可获取到有效值
        // 当key不存在时,获取到的结果将是null
        Serializable serializable = redisTemplate.opsForValue()
                .get("category");
        System.out.println("get value --> " + serializable);
        if (serializable != null) {
            CategoryDetailsVO category = (CategoryDetailsVO) serializable;
            System.out.println("get value --> " + category);
        }
    }

    @Test
    void testDeleteKey() {
        // 删除key时,将返回“是否成功删除”
        // 当key存在时,将返回true
        // 当key不存在时,将返回false
        Boolean result = redisTemplate.delete("name");
        System.out.println("result --> " + result);
    }

    @Test
    void testRightPushList() {
        // 存入List时,需要redisTemplate.opsForList()得到针对List的操作器
        // 通过rightPush()可以向Redis中的List追加数据
        // 每次调用rightPush()时使用的key必须是同一个,才能把多个数据放到同一个List中
        List<CategoryDetailsVO> list = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            CategoryDetailsVO category = new CategoryDetailsVO();
            category.setName("类别00" + i);
            list.add(category);
        }

        String key = "categoryList";
        for (CategoryDetailsVO category : list) {
            redisTemplate.opsForList().rightPush(key, category);
        }
    }

    @Test
    void testListSize() {
        // 获取List的长度,即List中的元素数量
        String key = "categoryList";
        Long size = redisTemplate.opsForList().size(key);
        System.out.println("size --> " + size);
    }

    @Test
    void testRange() {
        // 调用opsForList()后再调用range(String key, long start, long end)方法取出List中的若干个数据,将得到List
        // long start:起始下标(结果中将包含)
        // long end:结束下标(结果中将包含),如果需要取至最后一个元素,可使用-1作为此参数值
        String key = "categoryList";
        List<Serializable> range = redisTemplate.opsForList().range(key, 0, -1);
        for (Serializable serializable : range) {
            System.out.println(serializable);
        }
    }

    @Test
    void testKeys() {
        // 调用keys()方法可以找出匹配模式的所有key
        // 在模式中,可以使用星号作为通配符
        Set<String> keys = redisTemplate.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }
    }

}