SpringBoot自定義注解API數(shù)據(jù)加密和簽名校驗
api數(shù)據(jù)數(shù)據(jù)簽名(MD5,SHA1)
簽名枚舉類SginEnum.java
package com.jx.app.api.framework.annotation.enums;
/**
* @ClassName: SginEnum
* @Description: TODO(這是一個簽名枚舉類)
* @author gangyu
* @date 2018年11月20日 下午4:30:44
*/
public enum SginEnum {
//0不需要簽名,1使用MD5數(shù)據(jù)加密 2 使用SHA數(shù)據(jù)加密
ANY(0), MD5(1), SHA1(2);
private final int value;
private SginEnum(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
簽名注解類SginAnot.java
/**
* @Title: SginAnot.java
* @Package com.jxkj.app.api.framework
* @Description: TODO(用一句話描述該文件做什么)
* @author gangyu
* @date 2018年11月20日 下午4:07:58
* @version V1.0
*/
package com.jx.app.api.framework.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.jx.app.api.framework.annotation.enums.SginEnum;
/**
* @ClassName: SginAnot
* @Description: TODO(簽名驗證注解)
* @author gangyu
* @date 2018年11月20日 下午4:07:58
*
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SginAnot {
SginEnum type() default SginEnum.ANY;//默認不需要簽名
}
加密工具類MD5.java
package com.jx.common.entrypt;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jx.common.utils.BeanUtil;
import com.jx.common.utils.TestEntity;
/**
* @ClassName: MD5
* @Description: TODO(MD5加密工具)
* @author gangyu
* @date 2018年11月20日 下午2:12:14
*
*/
public class MD5 {
private static final Logger log = LoggerFactory.getLogger(MD5.class);
public static String PRIVATE_KEY = "這是你的密鑰";
private static final String hexDigIts[] = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
public static String encrypt(String plainText) {
try {
return encrypt(plainText,true);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
log.error("MD5加密異常:",e);
return null;
}
}
/**
* @Title: encrypt
* @Description: TODO(16位或32位密碼)
* @param @param
* plainText
* @param @param
* flag true為32位,false為16位
* @throws UnsupportedEncodingException
*/
public static String encrypt(String plainText, boolean flag) throws UnsupportedEncodingException {
try {
if (StringUtils.isEmpty(plainText)) {
return null;
}
MessageDigest md = MessageDigest.getInstance("MD5");
String encrStr = byteArrayToHexString(md.digest(plainText.getBytes("UTF-8")));
if (flag)
return encrStr;
else
return encrStr.substring(8, 24);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
@SuppressWarnings("unchecked")
public static String encrypt(Object obj){
if(obj==null){
return null;
}
Map<String, Object> map = new HashMap<String,Object>();
if(obj instanceof Map){
map=(Map<String, Object>) obj;
}else{
map = BeanUtil.transBean2Map(obj);
}
return encrypt(map,true);
}
/**
* @Title: encrypt
* @Description: TODO(16位或32位密碼)
* @param @param
* plainText
* @param @param
* flag true為32位,false為16位
* @throws UnsupportedEncodingException
*/
public static String encrypt(Map<String, Object> map, boolean flag) {
String param = null;
map.remove("sign");
map.remove("encrypt");
String result = BeanUtil.mapOrderStr(map);
if (StringUtils.isEmpty(result)) {
return null;
}
param = encrypt(encrypt(result)+PRIVATE_KEY);
if (flag) {
return param;
} else {
param = param.substring(8, 24);
}
return param;
}
public static Map<String, Object> resultMap = new HashMap<String, Object>();
@SuppressWarnings("unchecked")
public static Map<String, Object> mapFn(Map<String, Object> map) {
for (String key : map.keySet()) {
if (map.get(key) != null && map.get(key) != "" && (!key.equals("BTYPE") && !key.equals("SIGN"))) {
if (key.equals("INPUT")) {
if (map.get(key) != null) {
mapFn((Map<String, Object>) map.get(key));
}
} else {
resultMap.put(key, map.get(key));
}
}
}
return resultMap;
}
@SuppressWarnings("unchecked")
public static boolean check(Object obj){
Map<String,Object> map=new HashMap<String,Object>();
if(obj==null){
return false;
}
if(obj instanceof Map){
map=(Map<String, Object>) obj;
}else{
map = BeanUtil.transBean2Map(obj);
}
String sign=(String)map.get("sign");
if(sign==null){
return false;
}
String str=encrypt(obj);
return sign.equals(str)?true:false;
}
public 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();
}
public 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 void main(String[] args) throws UnsupportedEncodingException {
TestEntity test = new TestEntity();
test.setId("1");
test.setAge("20");
test.setClaes("你好");
test.setName("gyu");
test.setCreateTime("2018-11-20");
test.setSign("5189bd815c3850395f30779d0e59229e");
System.out.println("MD5驗簽成功:"+check(test));
}
}
SHA加密工具類SHA1.java
package com.jx.common.entrypt;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jx.common.utils.BeanUtil;
/**
* @ClassName: Sha1Util
* @Description: TODO(SHA加密)
* @author gangyu
* @date 2018年11月20日 下午2:08:36
*
*/
public class Sha1 {
private static final Logger log = LoggerFactory.getLogger(Sha1.class);
public static String encrypt(String str){
if (null == str || 0 == str.length()){
return null;
}
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'};
try {
MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
mdTemp.update(new String(str.getBytes("iso8859-1"), "utf-8").getBytes());
byte[] md = mdTemp.digest();
int j = md.length;
char[] buf = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
buf[k++] = hexDigits[byte0 & 0xf];
}
return new String(buf);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
log.error("SHA1加密異常:",e);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
log.error("SHA1加密異常:",e);
}
return str;
}
@SuppressWarnings("unchecked")
public static String encrypt(Object obj) {
if(obj==null){
return null;
}
Map<String, Object> map = new HashMap<String,Object>();
if(obj instanceof Map){
map=(Map<String, Object>) obj;
}else{
map = BeanUtil.transBean2Map(obj);
}
map.remove("sign");
map.remove("encrypt");
String result = BeanUtil.mapOrderStr(map);
if (StringUtils.isEmpty(result)) {
return null;
}
return encrypt(result);
}
@SuppressWarnings("unchecked")
public static boolean check(Object obj){
Map<String,Object> map=new HashMap<String,Object>();
if(obj==null){
return false;
}
if(obj instanceof Map){
map=(Map<String, Object>) obj;
}else{
map = BeanUtil.transBean2Map(obj);
}
String sign=(String)map.get("sign");
if(sign==null){
return false;
}
String str=encrypt(obj);
return sign.equals(str)?true:false;
}
}
返回數(shù)據(jù)對稱加密DES.java
package com.jx.common.entrypt;
import java.security.Key;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import com.google.gson.Gson;
import com.jx.common.utils.Base64;
/**
* @ClassName: Des
* @Description: TODO(對稱加密工具)
* @author gangyu
* @date 2018年11月20日 下午1:18:49
*/
public class DES {
// 密鑰
private final static String secretKey = "123456789987654321";
// 向量
private final static String iv = "ggboy123";
// 加解密統(tǒng)一使用的編碼方式
private final static String encoding = "utf-8";
/**
* @Title: encode
* @Description: TODO(加密)
* @param String
* @author gangyu2
* @date 2018年11月20日下午1:19:19
*/
public static String encode(String plainText){
Key deskey = null;
DESedeKeySpec spec;
try {
spec = new DESedeKeySpec(secretKey.getBytes());
SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede");
deskey = keyfactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
IvParameterSpec ips = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, deskey, ips);
byte[] encryptData = cipher.doFinal(plainText.getBytes(encoding));
return Base64.encode(encryptData);
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* @Title: decode
* @Description: TODO(解密)
* @param String
* @author gangyu2
* @date 2018年11月20日下午1:19:37
*/
public static String decode(String encryptText){
try{
Key deskey = null;
DESedeKeySpec spec = new DESedeKeySpec(secretKey.getBytes());
SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede");
deskey = keyfactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
IvParameterSpec ips = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, deskey, ips);
byte[] decryptData = cipher.doFinal(Base64.decode(encryptText));
return new String(decryptData, encoding);
}catch(Exception e){
e.printStackTrace();
return "";
}
}
}
BeanUtil.java
package com.jx.common.utils;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.lang3.StringUtils;
public class BeanUtil {
// Map --> Bean 2: 利用org.apache.commons.beanutils 工具類實現(xiàn) Map --> Bean
public static void transMap2Bean2(Map<String, Object> map, Object obj) {
if (map == null || obj == null) {
return;
}
try {
BeanUtils.populate(obj, map);
} catch (Exception e) {
System.out.println("transMap2Bean2 Error " + e);
}
}
// Map --> Bean 1: 利用Introspector,PropertyDescriptor實現(xiàn) Map --> Bean
public static void transMap2Bean(Map<String, Object> map, Object obj) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo
.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (map.containsKey(key)) {
Object value = map.get(key);
// 得到property對應(yīng)的setter方法
Method setter = property.getWriteMethod();
setter.invoke(obj, value);
}
}
} catch (Exception e) {
System.out.println("transMap2Bean Error " + e);
}
return;
}
// Bean --> Map 1: 利用Introspector和PropertyDescriptor 將Bean --> Map
public static Map<String, Object> transBean2Map(Object obj) {
if (obj == null) {
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
// 過濾class屬性
if (!key.equals("class")) {
// 得到property對應(yīng)的getter方法
Method getter = property.getReadMethod();
Object value = getter.invoke(obj);
if(null !=value && !"".equals(value))
map.put(key, value);
}
}
} catch (Exception e) {
System.out.println("transBean2Map Error " + e);
}
return map;
}
public static String mapOrderStr(Map<String, Object> map) {
List<Map.Entry<String, Object>> list = new ArrayList<Map.Entry<String, Object>>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Object>>() {
public int compare(Entry<String, Object> o1, Entry<String, Object> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> mapping : list) {
sb.append(mapping.getKey() + "=" + mapping.getValue() + "&");
}
return sb.substring(0, sb.length() - 1);
}
/**
*
* 將源的屬性復(fù)制到目標(biāo)屬性上去
* @param src
* @param dest
* @lastModified
* @history
*/
public static void copyProperties(Object dest,Object src) {
if (src == null || dest == null) {
return;
}
// 獲取所有的get/set 方法對應(yīng)的屬性
PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(src);
for (int i = 0; i < descriptors.length; i++) {
PropertyDescriptor propItem = descriptors[i];
// 過濾setclass/getclass屬性
if ("class".equals(propItem.getName())) {
continue;
}
try {
Method method = propItem.getReadMethod();
// 通過get方法獲取對應(yīng)的值
Object val = method.invoke(src);
// 如果是空,不做處理
if (null == val) {
continue;
}
if(val instanceof String) {
if(StringUtils.isBlank(val.toString())) {
continue;
}
}
// 值復(fù)制
PropertyDescriptor prop = propertyUtilsBean.getPropertyDescriptor(dest, propItem.getName());
// 調(diào)用寫方法,設(shè)置值
if (null != prop && prop.getWriteMethod() != null) {
prop.getWriteMethod().invoke(dest, val);
}
} catch (Exception e) {
}
}
}
public static <T> T mapToEntity(Map<String, Object> map, Class<T> entity) {
T t = null;
try {
t = entity.newInstance();
for(Field field : entity.getDeclaredFields()) {
if (map.containsKey(field.getName())) {
boolean flag = field.isAccessible();
field.setAccessible(true);
Object object = map.get(field.getName());
if (object!= null && field.getType().isAssignableFrom(object.getClass())) {
field.set(t, object);
}
field.setAccessible(flag);
}
}
return t;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return t;
}
}
攔截器AppInterceptor.java
package com.jx.app.api.framework.interceptor;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import com.google.gson.Gson;
import com.jx.app.api.framework.annotation.LoginAnot;
import com.jx.app.api.framework.annotation.SginAnot;
import com.jx.app.api.framework.annotation.enums.Auth;
import com.jx.app.api.framework.annotation.enums.SginEnum;
import com.jx.common.entrypt.MD5;
import com.jx.common.entrypt.Sha1;
import com.jx.common.token.ServiceException;
import com.jx.common.token.TokenException;
import com.jx.common.token.TokenUtil;
import com.jx.common.utils.Constants;
import com.jx.common.utils.RequestUtil;
import com.jx.common.utils.Result;
import com.jx.common.utils.enums.CodeEnum;
import com.jx.core.api.model.BaseModel;
/**
* @ClassName: AppInterceptor
* @Description: TODO(攔截器)
* @author gangyu
* @date 2018年11月20日 下午4:10:55
*
*/
public class AppInterceptor implements HandlerInterceptor{
private final static Logger LOGGER = LoggerFactory.getLogger(AppInterceptor.class);
private static final String CONTENT_TYPE="text/json;charset=UTF-8";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler.getClass().isAssignableFrom(HandlerMethod.class)) {
//簽名驗證注解
SginAnot signAnot = ((HandlerMethod) handler).getMethodAnnotation(SginAnot.class);
//驗簽
if(signAnot!=null && !signCheck(request,signAnot.type())){
response.setContentType(CONTENT_TYPE);
response.getWriter().print(new Gson().toJson(new Result(CodeEnum.SIGN_FAIL)));
return false;
}
return true;
}else{
return true;
}
}
private boolean signCheck(HttpServletRequest request,SginEnum enumm){
Map<String,Object> map=RequestUtil.parseRequset(request);
if(enumm==SginEnum.MD5){
return MD5.check(map);
}else if(enumm==SginEnum.SHA1){
return Sha1.check(map);
}
return false;
}
}
}
}
統(tǒng)一返回對象Result.java
package com.jx.common.utils;
import java.io.Serializable;
import com.google.gson.Gson;
import com.jx.common.entrypt.DES;
import com.jx.common.utils.enums.CodeEnum;
/**
* 結(jié)果DTO
* @lastModified
* @history
*/
public class Result implements Serializable{
private static final long serialVersionUID = 1L;
/**
* 結(jié)果詳細
*/
private String msg;
/**
* 需要傳回頁面的數(shù)據(jù)
*/
private Object data;
/**
* 狀態(tài)碼
*/
private String code;
/**
* 加密
*/
private boolean encrypt;
/**
* 圖標(biāo)
*/
private Integer icon;
public boolean isEncrypt() {
return encrypt;
}
public void setEncrypt(boolean encrypt) {
this.encrypt = encrypt;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Result() {
}
public Result(CodeEnum enums) {
this.msg = enums.getMsg();
this.encrypt = enums.getEncrypt();
this.code = enums.getCode();
this.icon = enums.getIcon();
}
public Result(CodeEnum enums,Object data) {
this.msg = enums.getMsg();
this.encrypt = enums.getEncrypt();
this.code = enums.getCode();
this.icon=enums.getIcon();
if(enums.getEncrypt()){
this.data=DES.encode(new Gson().toJson(data));
}else{
this.data=data;
}
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Integer getIcon() {
return icon;
}
public void setIcon(Integer icon) {
this.icon = icon;
}
public static Result success(CodeEnum enums) {
Result dto = new Result();
dto.setMsg(enums.getMsg());
dto.setEncrypt(enums.getEncrypt());
dto.setCode(enums.getCode());
dto.setIcon(enums.getIcon());
return dto;
}
public static Result success(CodeEnum enums,Object data) {
Result dto = new Result();
dto.setData(data);
dto.setEncrypt(enums.getEncrypt());
dto.setCode(enums.getCode());
dto.setIcon(enums.getIcon());
if(enums.getEncrypt()){
dto.setData(DES.encode(new Gson().toJson(data)));
}else{
dto.setData(data);
}
return dto;
}
public static Result fail(String msg) {
Result dto = new Result();
dto.setMsg(msg);
dto.setEncrypt(false);
dto.setCode("1");
dto.setIcon(SysCode.ICON.ICON_FAIL);
return dto;
}
}
狀態(tài)碼枚舉類CodeEnum.java
package com.jx.common.utils.enums;
/**
* @ClassName: ExceptionEnum
* @Description: TODO(系統(tǒng)編碼)
* @author gangyu
* @date 2018年11月21日 下午5:22:32
*/
public enum CodeEnum {
//系統(tǒng)編碼
SYS_EXCEPTION("999",false,"系統(tǒng)異常",'fail'),
SUCCESS("0",false,"成功",'success'),
ENCRYPT("0",true,"成功",'success'),
FAIL("1",false,"失敗",'fail'),
SIGN_FAIL("1",false,"簽名不正確",'fail'),
DATA_EMPTY("0",false,"暫無數(shù)據(jù)",'success'),
;
private String code;
private String msg;
private Boolean encrypt;
private Integer icon;
CodeEnum(String code,Boolean encrypt, String msg,Integer icon) {
this.code = code;
this.encrypt = encrypt;
this.msg = msg;
this.icon = icon;
}
public Integer getIcon() {
return icon;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
public Boolean getEncrypt() {
return encrypt;
}
}
Controller層使用簽名注解
@RequestMapping(value="encryptEntity",produces = "application/json;charset=UTF-8",method=RequestMethod.POST)
@SginAnot(type = SginEnum.MD5)
public Object encryptEntity(TestEntity test){
return businessService.encryptEntity(test);
}
- 驗簽加密排序根據(jù)ASII表進行排序在BeanUtil.java類里已經(jīng)實現(xiàn)
- url?a=1&c=2&b=3。。。->排序后 a=1&b=3&c=2 然后進行數(shù)據(jù)加密 倆邊排序方式要一致當(dāng)然為了安全可以排完序加入自己的密鑰在進行一次加密
- Result.java提供了返回數(shù)據(jù)加密方法CodeEnum.ENCRYPT這里使用的是DES對稱加密
到此這篇關(guān)于SpringBoot自定義注解API數(shù)據(jù)加密和簽名校驗的文章就介紹到這了,更多相關(guān)SpringBoot 數(shù)據(jù)加密和簽名校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中Optional.of()方法及源碼解析(非常詳細!)
這篇文章主要給大家介紹了關(guān)于Java中Optional.of()方法及源碼解析的相關(guān)資料,Java中java.util .Optional類的of()方法用于獲得該Optional類中具有指定類型的指定值的一個實例,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-06-06
IDEA中application.properties的圖標(biāo)顯示不正常的問題及解決方法
這篇文章主要介紹了IDEA中application.properties的圖標(biāo)顯示不正常的問題及解決方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
Java?webservice的POST和GET請求調(diào)用方式
這篇文章主要介紹了Java?webservice的POST和GET請求調(diào)用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Spring Boot自定義Banner實現(xiàn)代碼
這篇文章主要介紹了Spring Boot自定義Banner實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01
SpringBoot整合Spring?Data?JPA的詳細方法
JPA全稱為Java Persistence API(Java持久層API),是一個基于ORM的標(biāo)準(zhǔn)規(guī)范,在這個規(guī)范中,JPA只定義標(biāo)準(zhǔn)規(guī)則,不提供實現(xiàn),本文重點給大家介紹SpringBoot整合Spring?Data?JPA的相關(guān)知識,感興趣的朋友一起看看吧2022-02-02

