数据库配置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-webapi
的CategoryMapper.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-webapi
的src\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);
}
}
}
本文由 liyunfei 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Oct 6,2022