澳门新蒲京娱乐

新蒲京官方下载 7
Bean配置与注入相关【新蒲京官方下载】
澳门新蒲京娱乐 3
基于SPI的协议加固透明传输工具,网络七层协议的形象说明

如何使用Spring,Boot从0到1搭建一个Java后台

加密与解密

Base64算法

Base64算法最先选择于消除电子邮件传输的主题素材。Base64是一种基于六12个字符的编码算法,依据WranglerFC
2045的概念:Base64内容传送编码是一种以自由8位字节类别组合的描述情势,这种形式不错被凡直接识别。经过Base64编码后的数码会比原本数据略长,为原来的4/3倍。经Base64编码后的字符串的字符数是以4为单位的大背头倍。Base64算法在org.springframework.util包中有落到实处,上面来测量检验下base64的编码与解码,如下所示:

@RunWith(SpringRunner.class)@SpringBootTestpublic class EncryptTests { @Test public void testBase64() { String str = "123456"; String encodeStr = Base64Utils.encodeToString(str.getBytes; System.out.println("base64 encode str : " + encodeStr); String decodeStr = new String(Base64Utils.decodeFromString(encodeStr)); System.out.println("base64 decode str : " + decodeStr); Assert.assertEquals(str, decodeStr); }}

假如str等于decodeStr则证实地衡量试通过,日志音讯如下:

图片 1spring-boot
1-7.png

MD5算法

MD5算法重要用以评释数据的完整性,md5算法的贯彻分为多少个部分,一部分是对字符串进行加密,一部分是对文本进行加密,完成如下所示:

public class MD5Util { private static byte[] hexBase = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102}; public static String fileHash(String filePath) { return fileHash(new File); } /** * 用DigestInputStream来计算大文件的md5,也避免内存吃不消。 */ public static String fileHash(File file) { if (file == null) { return ""; } try { int bufferSize = 1024 * 1024; MessageDigest messageDigest = MessageDigest.getInstance; FileInputStream fis = new FileInputStream; DigestInputStream dis = new DigestInputStream(fis, messageDigest); byte[] buffer = new byte[bufferSize]; while (dis.read > 0) ; messageDigest = dis.getMessageDigest(); byte[] result = messageDigest.digest(); return byteArrayToHex; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return ""; } public static String stringMD5(String string) { return stringMD5(string, "utf-8"); } public static String stringMD5(String string, String charsetName) { if (StringUtils.isEmpty { return ""; } try { byte[] data = string.getBytes(charsetName); MessageDigest messageDigest = MessageDigest.getInstance; messageDigest.update; byte[] result = messageDigest.digest(); return byteArrayToHex; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } /** * md5对字符串加密 * 方法一:使用hexBase 48, 49, 50, 51 * 方法二:hexDigts 0 1 2 3 4 5 * 这两种方法得到的md5加密结果是一样的。 * 实现字节数组到十六进制的转换 */ public static String byteArrayToHex(byte[] bytes) { if (ArrayUtils.isEmpty { return ""; } StringBuffer stringBuffer = new StringBuffer(); int length = bytes.length; for (int i = 0; i < length; i++) { stringBuffer.append hexBase[((bytes[i] & 0xF0) >> 4)]); stringBuffer.append hexBase[(bytes[i] & 0xF)]); } return stringBuffer.toString(); } public static String byteArrayToHex2(byte[] byteArray) { char[] hexDigits = {'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F' }; char[] resultCharArray =new char[byteArray.length * 2]; int index = 0; for (byte b : byteArray) { resultCharArray[index++] = hexDigits[b>>>4 & 0xf]; resultCharArray[index++] = hexDigits[b & 0xf]; } return new String(resultCharArray).toLowerCase(); } public static String createMD5Sign(SortedMap signMap, String key) { StringBuffer stringBuffer = new StringBuffer(); Set<Map.Entry<String, String>> paramSet = signMap.entrySet(); for (Map.Entry<String, String> entry : paramSet) { String k = entry.getKey(); if (k.equals || k.equals || k.equals { continue; } String v = entry.getValue(); stringBuffer.append(k + "=" + v + "&"); } String params = stringBuffer.append("key=" + key).toString(); return stringMD5(params, "utf-8").toUpperCase(); }}

先对字符串md5加密扩充下测验,如下:

@Testpublic void testMD5Str() { String str = "456789123456"; String md5Str = MD5Util.stringMD5; System.out.println("md5 str : " + md5Str); Assert.assertTrue(md5Str.length;}

表达下何以md5Str的尺寸等于32就以为md5加密成功了,因为md5算法须求获得三个专断长度的信息并爆发二个1二十六人的音信摘要,若是将以此1贰拾五位的二进制摘要音讯换算成十六进制,能够赢得贰个32个人的字符串。日志新闻如下:

图片 2spring-boot
1-8.png

再测验一下对文本进行md5加密,文件为电脑桌面的一张图片,如下:

@Testpublic void testMD5File() { File file = new File("/Users/Bruce/Desktop/hrm4.jpg"); String md5File = MD5Util.fileHash; System.out.println("md5 file : " + md5File); Assert.assertTrue(md5File.length;}

日志信息如下:

图片 3spring-boot
1-9.png

在对大文件进行加密的时候,必定要采取DigestInputStream,那样可以制止内部存款和储蓄器吃不消,也能够使加密速度大大升高。

AES算法

AES算法的产出重大正是为了消除DES算法出现的尾巴,完成如下:

public class AESUtil { public static String encrypt(String data, String key, String iv) { return encrypt(data, key, iv, "utf-8"); } public static String encrypt(String data, String key, String iv, String charsetName) { try { return encrypt(data.getBytes(charsetName), key.getBytes(charsetName), iv.getBytes(charsetName)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return ""; } public static String encrypt(byte[] data, byte[] key, byte[] iv) { // 这里的key为了与iOS统一,不可以使用KeyGenerator、SecureRandom、SecretKey生成 SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec; try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] result = cipher.doFinal; // 对加密后的数据,尽心base64编码 return Base64Util.encode; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } return ""; } public static String decrypt(String data, String key, String iv){ return decrypt(data, key, iv, "utf-8"); } public static String decrypt(String data, String key, String iv, String charsetName) { try { byte[] contentData = Base64Util.decodeBytes(data.getBytes(charsetName)); return decrypt(contentData, key.getBytes(charsetName), iv.getBytes(charsetName), charsetName); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return ""; } public static String decrypt(byte[] data, byte[] key, byte[] iv, String charsetName) { SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec; try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] result = cipher.doFinal; return new String(result, charsetName); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return ""; }}

对AES算法实行下测量试验,如下:

@Testpublic void testAES() { String str = "0987654f321"; String encryptStr = AESUtil.encrypt(str, BootConstants.AES_KEY, BootConstants.AES_IV); System.out.println("encryptStr : " + encryptStr); String decryptStr = AESUtil.decrypt(encryptStr, BootConstants.AES_KEY, BootConstants.AES_IV); System.out.println("decryptStr : " + decryptStr); Assert.assertEquals(str, decryptStr);}

内部KEY、IV都以大家自定义的,具体能够看下Java加密与解密那本书,下面对KEY和IV有详尽的表明,日志新闻如下所示:

图片 4spring-boot
1-10.png

REST

REST是一种布满式应用的架构,也是一种大流量分布式应用的宏图方法论。REST的法文全称为Representational
State
Transfer,翻译成中文正是表现层状态转变。要知道RESTful架构,最棒的措施正是去通晓Representational
State
Transfer那些短语到底是何等看头,它的每二个词都表示了怎么涵义,把那么些搞懂了,就能够理解REST是一种怎么样的统一筹划。关于如何掌握REST,能够参照阮一峰先生的那篇小说。

援用一下阮一峰老师计算的RESTful架构:

  • 每贰个U大切诺基I代表一种财富。
  • 客商端和服务器之间,传递这种能源的某种表现层。
  • 顾客端通过多少个HTTP动词,对服务器端财富开展操作,完毕表现层状态转化。

知情完什么是RESTful架构后,大家看下在规划RESTful
Api要求介怀的地点,最广泛的荒唐正是UWranglerI中隐含动词。因为财富表示一种实体,所以理应是名词,UOdysseyI不应有有动词,动词应该放在HTTP左券中。举例有个别U路虎极光I为/hrm/users/show,个中show为动词,那样设计出来的UPRADOI就大错特错了。这么些U昂科雷I的指标是查询客商的列表,可是在UENVISIONI中不应有出现show动词,而是应该通过HTTP的GET方法表示show,所以准确的URubiconI应为GET
/hrm/users。下边通过一个不难的例证来验证下HTTP中的5种动作必要。

GET /hrm/users 查询用户的列表POST /hrm/users 增加一个用户DELETE /hrm/users/id 删除一个用户PUT /hrm/users/id 更新一个用户(提供该用户的全部信息)PATCH /hrm/users/id 更新一个用户(提供该用户的部分信息)

越来越多关于Restful Api设计能够参照阮一峰先生的另外一篇作品。

数据库

第一步,先是来成立二个部门表并插入几条数据,展开MySQLWorkbench,实施上边的sql语句:

# 创建数据库ddn_hrm_dbcreate database ddn_hrm_db;# 使用数据库ddn_hrm_dbuse ddn_hrm_db;# 创建表dept_infcreate table dept_inf ( id INT not null auto_increment, name varchar not null, remark varchar default null, primary key ;# 插入几条数据insert into dept_inf(id, name, remark) values (1, '技术部', '技术部'), (2, '运营部', '运营部'), (3, '财务部', '财务部'), (4, '总公办', '总公办'), (5, '市场部', '市场部');

用MySQLWorkbench查询下方才插入进去的数额,验证下表创建的是或不是准确、插入数据是不是科学,查询结果如下所示:

图片 5spring-boot
1-1.png

第二步,在Spring
Boot中布置mybatis,配置数据库连接池(使用Ali的Druid),先开拓pom.xml插足mybatis、druid所重视的jar包,配置如下所示:

<!-- Mybatis集成 --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version></dependency><dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version></dependency><!-- 阿里数据库连接池 --><dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.4</version></dependency>

继而展开application.properties加入JDBC配置、druid配置、mybatis配置,配置如下所示:

# JDBC配置spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/ddn_hrm_db?useUnicode=true&characterEncoding=utf8spring.datasource.druid.username=rootspring.datasource.druid.password=spring.datasource.driver-class-name=com.mysql.jdbc.Driver# 连接池配置spring.datasource.druid.initial-size=10spring.datasource.druid.max-active=50spring.datasource.druid.min-idle=10spring.datasource.druid.max-wait=60000spring.datasource.druid.time-between-eviction-runs-millis=60000spring.datasource.druid.min-evictable-idle-time-millis=300000# 监控配置# WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilterspring.datasource.druid.web-stat-filter.enabled=truespring.datasource.druid.web-stat-filter.url-pattern=/**spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*spring.datasource.druid.web-stat-filter.session-stat-enable=truespring.datasource.druid.web-stat-filter.session-stat-max-count=1000spring.datasource.druid.web-stat-filter.profile-enable=true# StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置spring.datasource.druid.stat-view-servlet.enabled=truespring.datasource.druid.stat-view-servlet.url-pattern=/druid/*spring.datasource.druid.stat-view-servlet.reset-enable=truespring.datasource.druid.stat-view-servlet.login-username=brucespring.datasource.druid.stat-view-servlet.login-password=bruce2017# Spring监控配置,说明请参考Druid Github Wiki,配置_Druid和Spring关联监控配置spring.datasource.druid.aop-patterns=com.dodonew.service.*,com.dodonew.dao.* # Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔# 如果spring.datasource.druid.aop-patterns要代理的类没有定义interface请设置spring.aop.proxy-target-class=true# Filter配置spring.datasource.druid.filters=stat,wall# logging配置logging.level.org.mybatis.spring=debug# 显示SQL日志logging.level.com.dodonew.dao=debug# mybatis配置mybatis.configuration.cache-enabled=truemybatis.configuration.jdbc-type-for-null=nullmybatis.configuration.call-setters-on-nulls=true

在地点的安插中,体现SQL日志的配置要非常注意下,在那之中com.dodonew.dao是您mapper类所在的包,独有这么安顿才干把sql日志给打字与印刷出来。

第三步,创立Dept域对象、DeptDao类、DeptService类,如下所示:

Dept域对象:

public class Dept implements Serializable { private static final long serialVersionUID = -4243387151355500160L; private Integer id; private String departName; private String remark; public Dept(Integer id, String departName, String remark) { this.id = id; this.departName = departName; this.remark = remark; } public Dept(String departName, String remark) { this.departName = departName; this.remark = remark; } public Dept() { } public Integer getId() { return id; } public String getDepartName() { return departName; } public String getRemark() { return remark; } public void setId(Integer id) { this.id = id; } public void setDepartName(String departName) { this.departName = departName; } public void setRemark(String remark) { this.remark = remark; } @Override public String toString() { return "Dept{" + ", departName='" + departName + '\'' + ", remark='" + remark + '\'' + '}'; }}

DeptDao、DeptDynaSqlProvider类:

public interface DeptDao { /** * 查询所有部门 * @return 所有部门 */ @Select("select * from " + HrmConstants.DEPTTABLE + " ") List<Dept> selectAllDept(); /** * 根据id查询部门 * @param id 部门id * @return 某个部门 */ @Select("select * from " + HrmConstants.DEPTTABLE + " where id = #{id}") Dept selectById(Integer id); /** * 根据id删除部门 * @param id 部门id */ @Delete("delete from " + HrmConstants.DEPTTABLE + " where id = #{id}") Integer deleteById(Integer id); /** * 查询总数量 * @param params * @return 部门总数量 */ @SelectProvider(type = DeptDynaSqlProvider.class, method = "count") Integer count(Map<String, Object> params); /** * 分页动态查询 * @param params * @return 部门列表 */ @SelectProvider(type = DeptDynaSqlProvider.class, method = "selectWithParams") List<Dept> selectByPage(Map<String, Object> params); /** * 动态插入部门 * @param dept * * @SelectKey 注解的主要作用就是把当前插入对象的主键值,赋值给对应的id属性(id代表对应的主键) */ @InsertProvider(type = DeptDynaSqlProvider.class, method = "insertDept") @SelectKey(statement = "SELECT LAST_INSERT_ID() AS id", keyProperty = "id", keyColumn = "id", before = false, resultType = Integer.class) Integer save(Dept dept); /** * 更新某个部门的信息 * @param dept */ @UpdateProvider(type = DeptDynaSqlProvider.class, method = "updateDept") Integer update(Dept dept);}public class DeptDynaSqlProvider { // 分页动态查询 public String selectWithParams(final Map<String, Object> params) { String sql = new SQL() { { SELECT; FROM(HrmConstants.DEPTTABLE); if (params.get != null) { Dept dept =  params.get; if (dept.getDepartName() != null && !"".equals(dept.getDepartName { WHERE(" departname like concat ('%', #{dept.departName}, '%') "); } } } }.toString(); if (params.get("pageModel") != null) { sql += " limit #{pageModel.firstLimitParam}, #{pageModel.pageSize}"; } return sql; } // 动态查询总数量 public String count(final Map<String, Object> params) { return new SQL(){ { SELECT"); FROM(HrmConstants.DEPTTABLE); if (params.get != null) { Dept dept =  params.get; if (dept.getDepartName() != null && !"".equals(dept.getDepartName { WHERE(" departname like concat ('%', #{dept.departName}, '%') "); } } } }.toString(); } // 动态插入 public String insertDept(final Dept dept) { return new SQL(){ { INSERT_INTO(HrmConstants.DEPTTABLE); if (dept.getDepartName() != null && !"".equals(dept.getDepartName { VALUES("departname", "#{departName}"); } if (dept.getRemark() != null && !"".equals(dept.getRemark { VALUES("remark", "#{remark}"); } } }.toString(); } // 动态更新 public String updateDept(final Dept dept) { return new SQL(){ { UPDATE(HrmConstants.DEPTTABLE); if (dept.getDepartName() != null) { SET(" departname = #{departName}"); } if (dept.getRemark() != null) { SET(" remark = #{remark}"); } WHERE(" id = #{id}"); } }.toString(); }}

DeptService、DeptServiceImpl类:

public interface DeptService { public List<Dept> findDeptList(Integer pageIndex, Integer pageSize); public Dept findDept(Integer id); public boolean addDept(Dept dept); public boolean removeDept(Integer id); public boolean modifyDept(Dept dept);}@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)@Service("deptService")public class DeptServiceImpl implements DeptService { @Autowired private DeptDao deptDao; @Override @Transactional(readOnly = true) public Dept findDept(Integer id) { return deptDao.selectById; } @Override public boolean addDept(Dept dept) { boolean isSuccess = false; try { // 这里row永远是返回受影响的行数 int row = deptDao.save; if (row > 0) { isSuccess = true; } } catch (Exception e) { isSuccess = false; } return isSuccess; } @Override public boolean removeDept(Integer id) { boolean isSucces = false; try { Integer row = deptDao.deleteById; if (row > 0) { isSucces = true; } } catch (Exception e) { isSucces = false; } return isSucces; } @Override public boolean modifyDept(Dept dept) { boolean isSuccess = false; try { int row = deptDao.update; if (row > 0) { isSuccess = true; } } catch (Exception e) { isSuccess = false; } return isSuccess; } @Override @Transactional(readOnly = true) public List<Dept> findDeptList(Integer pageIndex, Integer pageSize) { Map<String, Object> params = new HashMap<>(); PageModel pageModel = new PageModel(); pageModel.setPageIndex(pageIndex); if (pageSize <= 0) { pageSize = 10; } pageModel.setPageSize; Integer count = deptDao.count; pageModel.setRecordCount; params.put("pageModel", pageModel); return deptDao.selectByPage; }}

在这边要求极度注意的是,对数据开展扦插、删除、修改的时候,要回去二个boolean变量值,告诉调用者有未能如愿。对于mybatis来讲,插入、删除、修改成功的时候,都会回去受影响的行数,也正是说大于0的,失利的时候会重返0。在数码插入的时候,有这么一条配置:

@InsertProvider(type = DeptDynaSqlProvider.class, method = "insertDept")@SelectKey(statement = "SELECT LAST_INSERT_ID() AS id", keyProperty = "id", keyColumn = "id", before = false, resultType = Integer.class)Integer save(Dept dept);

内部@SelectKey的意义就是把最近安顿那条数据的主键id值,赋值给dept对象的id属性,那样就足以由此dept对象的id属性获取到mybatis数据库中的主键值,这对于有个别接口业务达成是要求的。

说起底一步,我们来写单元测验来证明下,对dept表进行增加和删除改查是或不是健康。

查询部门列表:

@RunWith(SpringRunner.class)@SpringBootTestpublic class DeptServiceTests { @Autowired private DeptService deptService; @Test public void testDeptList() { List<Dept> deptList = deptService.findDeptList; System.out.println("测试部门列表 : " + deptList); Assert.assertTrue(deptList.size; }}

日志打印新闻如下:

图片 6spring-boot
1-2.png

从日记音讯方可看出SQL日志给打字与印刷出来了,别的,部门列表也给正确打字与印刷出来了。那表达数据库配置、mybatis配置都不曾难点了,可以健康使用了。

充实三个机构:

@Testpublic void testAddDept() { Dept dept = new Dept("测试部1", "测试部1"); boolean isSuccess = deptService.addDept; System.out.println("增加部门的主键id:" + dept.getId; Assert.assertEquals(true, isSuccess);}@Test@Transactionalpublic void testAddDept2() { Dept dept = new Dept("测试部2", "测试部2"); boolean isSuccess = deptService.addDept; System.out.println("增加部门的主键id: " + dept.getId; Assert.assertEquals(true, isSuccess);}

那几个办法有哪些界别吗?大家来跑下看下差异,运维testAddDept()方法得到的日志消息如下:

图片 7spring-boot
1-3.png

可以看出增添部门的主键id为46,查询数据库能够见到该条记录已经存在数据库个中了。如下图所示:

图片 8spring-boot
1-4.png

运维testAddDept2()获得的日志消息如下:

图片 9spring-boot
1-5.png

能够看见扩张部门的主键id为47,然而查询数据库后却开采并未有id为47那条数据,查询数据库结果如下所示:

图片 10spring-boot
1-6.png

那是为啥呢?那是因为@Transactional评释的原故,大家看看主键的id为47,这注脚数据是插入成功了,不然的话id就不设有的,不过在这些测量检验方法推行完事后,事务举行了下回滚,所以数据库中就从未这条数据了。

其余改变、删除测验方法,这里就不在陈说了,大家能够团结实现下。

有关什么行使Spring
Boot从0到1搭建三个Java后台,重要分为三篇小说来介绍:第一篇作品主要介绍搭建二个Java后台必要的相干技能;第二篇小说首要介绍怎么着运用代码来贯彻贰个总体的后台接口;第三篇作品重要介绍本人在写叁个轻松易行java后台项目遇到的某些难题。本篇为率先篇文章。

接口

第一步,依据Restful接口设计,供给完成的接口列表如下所示:

GET /hrm/api/depts 查询部门列表GET /hrm/api/depts/id 查询指定部门信息POST /hrm/api/depts 增加一个部门DELETE /hrm/api/depts/id 删除指定部门PUT /hrm/api/depts/id 更新某个部门信息(需要提供整个部门的信息)PATCH /hrm/api/depts/id 更新某个部门信息(需要提供部门的部分信息)

第二步,自定义注明,因为乞求接口,有个别参数是必得求传的,这年就要用到自定义申明了,如下所示:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface DataValidate { String[] requiredParams() default {};}

其三步:配置拦截器,每多个伸手过来,都要先举行下验证,验证sign具名是或不是准确,验证央浼时间是不是行得通,验证必传参数是不是传过来了,那一个都须求接纳拦截器的,比方验证sign签字是还是不是科学:

 // 对数据签名进行校验 需要的是sortedMap,看看这样转换是不是可以的 String sign = jsonObject.getString; Map<String, Object> map = JSONObject.toJavaObject(jsonObject, Map.class); // TreeMap默认是升序排列的,如果要改为降序采用其他方式来实现 SortedMap<String, Object> sortedMap = new TreeMap<>; String sysSign = MD5Util.createMD5Sign(sortedMap, BootConstants.SIGN_KEY); if (!sysSign.equals { logger.info("数据签名校验失败"); // 在这里返回错误的信息给客户端 JSONObject responseJson = new JSONObject(); responseJson.put(BootConstants.CODE_KEY, StatusCode.ERROR_SIGN_INVALIDATE); responseJson.put(BootConstants.MESSAGE_KEY, "数据签名校验失败"); String jsonStr = JSON.toJSONString(responseJson, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty); String responseStr = AESUtil.encrypt(jsonStr, BootConstants.AES_KEY, BootConstants.AES_IV); response.getWriter().write(responseStr); return false; }

完全的拦截器代码,会在小说最终开源出来自个儿写的Java后台小品种

其三步,定义表的常量、项目其中要用到的一对常量,如下所示:

public class StatusCode { public static final Integer SUCCESS = 0; // 成功 public static final Integer ERROR = -1; //错误信息 // app端使用100开头的 public static final Integer ERROR_TIMEOUT = 100; // 请求时间不合法 public static final Integer ERROR_SIGN_INVALIDATE = 101; // 签名校验失败 public static final Integer ERROR_REQUIREDPARAMS_LOST = 102; // 必传参数未传 public static final Integer ERROR_DATA_EMPTY = 103; // 数据为空 public static final Integer ERROR_UNKNOW = 104; // 系统错误 // 后台网站使用200开头的 以后其他类型以此类推 通用的错误信息用状态码给表示出来,特殊的错误信息用code = -1来标识即可}public class HrmConstants { // 数据库常量表 public static final String USERTABLE = "user_inf"; public static final String DEPTTABLE = "dept_inf"; public static final String JOBTABLE = "job_inf"; public static final String EMPLOYEETABLE = "employee_inf"; public static final String NOTICETABLE = "notice_inf"; public static final String DOCUMENTTABLE = "document_inf"; // 登录 public static final String LOGIN = "loginForm"; // 用户的Session对象 public static final String USER_SESSION = "user_session"; // 默认每页4条数据 public static int PAGE_DEFAULT_SIZE = 4;}

于今大家来写个单元测量检验,来验证下配置的拦截器是不是起效果,测量检验时间是或不是行得通为例,剩下的能够在开源项目中找到:

@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvcpublic class DataSecurityInterceptorTests { @Autowired private MockMvc mockMvc; @Test public void testTimeValidate() { SortedMap<String, String> sortedMap = new TreeMap<>(); //sortedMap.put("timeStamp", System.currentTimeMillis; sortedMap.put("timeStamp", "1509416666000"); sortedMap.put("deptId", "1"); StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry<String, String> entry : sortedMap.entrySet { stringBuilder.append(entry.getKey() + "=" + entry.getValue; } String sign = MD5Util.createMD5Sign(sortedMap, BootConstants.SIGN_KEY); System.out.println("sign : " + sign); sortedMap.put("sign", sign); String mapStr = JSON.toJSONString(sortedMap, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty); String encryptStr = AESUtil.encrypt(mapStr, BootConstants.AES_KEY, BootConstants.AES_IV); try { MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/hrm/api/depts/id") .param("Encrypt", encryptStr) .accept(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status .andDo(MockMvcResultHandlers.print .andReturn(); String content = mvcResult.getResponse().getContentAsString(); System.out.println("content = " + content); String decryptStr = AESUtil.decrypt(content, BootConstants.AES_KEY, BootConstants.AES_IV); System.out.println("解密后的字符串: " + decryptStr); } catch (Exception e) { e.printStackTrace(); } }}

第四步,编写DeptController的代码,以取得部门列表,别的完整的,能够在篇章最终开源的品类中找到:

@RestControllerpublic class DeptController { private static final Logger logger = LoggerFactory.getLogger(DeptController.class); @Autowired @Qualifier("deptService") private DeptService deptService; /** * 在查询列表的时候,有这样一个情况:如果当前页面是否超过了总页数:如果超过了默认给最后一页作为当前页。 */ @RequestMapping(value = "/hrm/api/depts", method = RequestMethod.GET) @DataValidate(requiredParams = {"pageIndex"}) public void selectDeptList(HttpServletRequest request, HttpServletResponse response) { JSONObject requestJson = (JSONObject) request.getAttribute(BootConstants.REQUESTDATA); if (!requestJson.isEmpty { JSONObject resultJson = new JSONObject(); String pageIndex = requestJson.getString("pageIndex"); String pageSize = requestJson.getString("pageSize"); if (StringUtils.isEmpty) { pageSize = "10"; } List<Dept> deptList = deptService.findDeptList(Integer.parseInt(pageIndex), Integer.parseInt); resultJson.put(BootConstants.CODE_KEY, StatusCode.SUCCESS); resultJson.put(BootConstants.MESSAGE_KEY, "请求成功"); if (deptList != null && deptList.size { String deptStr = JSON.toJSONString(deptList, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty); JSONArray deptJsonArray = JSONArray.parseArray; resultJson.put(BootConstants.DATA_KEY, deptJsonArray); } else { JSONArray emptyJsonArray = new JSONArray(); resultJson.put(BootConstants.DATA_KEY, emptyJsonArray); } request.setAttribute(BootConstants.REQUESTAFTERDATA, resultJson); } }}

在编辑Controller代码的时候,非常要留神fastjson的主题材料,fastjson私下认可对列表会设有循环引用,也便是会并发ref的气象,那一年就要把循环引用给关闭掉。其他,fastjson暗中同意也会把属性值为null或许空的给过滤掉,不显得出来,要安装其类别化的属性本事够让其给展现出来。这点认为fastjson设计的不太友好,大概阿里Baba(Alibaba)之中因为业务需要是那般须求的,对Alibaba里面采纳也许相比方便,可是开源出来,面向公众的零件,就不应有如此设计了,嘲讽一下。

第五步,大家由此三种测量试验来表明下央求部门列表是不是常常,第一种采纳mockMvc来进展测量试验,第二种采纳httpclient来发送真实的互联网乞请来测量试验,首先看下第一种测量检验,如下所示:

@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvcpublic class DeptControllerTests { @Autowired private MockMvc mockMvc; @MockBean private DeptService deptService; @Before public void setUp() throws Exception { Dept dept = new Dept(); dept.setId; dept.setDepartName; dept.setRemark; // given的主要作用就是对controller下面的接口进行下快速验证而已,并不会发所有的数据全部返回给你的。 BDDMockito.given(deptService.findDeptList.willReturn(Arrays.asList; } @Test public void testDeptList() throws Exception { SortedMap<String, String> sortedMap = new TreeMap<>(); sortedMap.put("timeStamp", System.currentTimeMillis; sortedMap.put("pageIndex", "1"); StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry<String, String> entry : sortedMap.entrySet { stringBuilder.append(entry.getKey() + "=" + entry.getValue; } String sign = MD5Util.createMD5Sign(sortedMap, BootConstants.SIGN_KEY); System.out.println("sign : " + sign); sortedMap.put("sign", sign); String mapStr = JSON.toJSONString(sortedMap, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty); String encryptStr = AESUtil.encrypt(mapStr, BootConstants.AES_KEY, BootConstants.AES_IV); MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/hrm/api/depts") .param("Encrypt", encryptStr) .accept(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status .andDo(MockMvcResultHandlers.print .andReturn(); String content = mvcResult.getResponse().getContentAsString(); System.out.println("content = " + content); String decryptStr = AESUtil.decrypt(content, BootConstants.AES_KEY, BootConstants.AES_IV); JSONObject resultJson = JSONObject.parseObject(decryptStr); System.out.println("部门列表测试信息 : " + resultJson); }}

日记信息如下所示:

图片 11spring-boot
1-11.png

使用httpClient来张开下测量检验,如下所示:

@RunWith(SpringRunner.class)@SpringBootTestpublic class DeptHttpClientTests { /** * 测试GET方法:部门列表、单独一个部门 */ @Test public void testDeptList() { SortedMap<String, String> sortedMap = new TreeMap<>(); sortedMap.put("pageIndex", "1"); String encryptStr = EncryptUtils.getEncryptStr(sortedMap); try { URIBuilder builder = new URIBuilder(BootConstants.LOCAL_HOST+"/hrm/api/depts") .addParameter("Encrypt", encryptStr); String content = HttpUtils.sendGetRequest(builder.build; System.out.println("content = " + content); String decryptStr = EncryptUtils.getDecryptStr; JSONObject resultJson = JSONObject.parseObject(decryptStr); System.out.println("部门列表测试信息 : " + resultJson); } catch (URISyntaxException e) { e.printStackTrace(); } }}

日志音信如下:

图片 12spring-boot
1-12.png

从日记消息方可看看两岸的区分了,使用mockMvc的格局只是把假定的列表值给打字与印刷出来了,这种措施也证实了其查询部门列表方法是马到功成的。使用httpclient发送真实的网络恳求把具备的机关列表值都给打印出来了。三种办法都有现实的运用景况,读者能够依据本身的急需去接纳。

接口安全与加密

现行反革命大多数后台接口都选拔RESTful架构,RESTful
Api的居民身份证应该运用OAuth2.0框架,也正是说顾客端在历次诉求时都要带上身份验证消息,完成上,大多数都利用token的注明方法,日常流程如下所示:

  1. 客户登入成功后,服务器端重返token和refreshToken给顾客端。
  2. 顾客端把token、refreshToken保存到本地,未来发送的各样诉求,都将token发回服务器端。
  3. 劳务器端检查token的卓有成效,有效则赶回数据,若无效,分为二种景况:
    • token错误,客商供给再次登入,获取科学的token。
    • token过期,客商重新登入,使用refreshToken重新获得token。

使用OAuth2.0进展api身份验证,相比相符对外提供的api,对于公司内部的api,常常采取接口加密的艺术来张开落实。在这一个Java后台项目中,就是使用的接口加密的格局,首要使用了md5加密算法和AES加密算法。

MD5算法是头角崭然的新闻摘要算法,常用于注解数据的完整性,是数字签字算法的骨干算法。其前身有MD2、MD3和MD4算法,它由MD4、MD3、MD2算法革新而来。不论哪一类MD算法,它们都亟需得到三个随机长度的新闻并发生一个1二十六位的音讯摘要。因为MD5算法常用于注解数据的完整性,所以客户端在发送二个伸手的时候,客商端对央求所传的参数进行MD5计算进而获得二个sign具名,传递给劳务器端。服务器端,得到顾客端传递过来的参数再度进行MD5计量获得一个新的sign签字。假使顾客端传递过来的sign签字和服务器端总计出来的sign签字相等,则证实数据是完整性的,不然,数据在传输的进程中已经被篡改了。

AES加密算法是对称加密算法,AES算法因密钥构造建设即间短、灵敏性好、内部存款和储蓄器须要低级优点,应用的可比常见,并且近些日子采纳的AES算法能够有效抵御已知的针对DES算法的富有攻击方式。在地方大家只是对参数进行MD5总结拿到多个sign具名,用来校验数据是或不是完整性,而是多少在传输的历程中,大家还要对传输的数目开展下AES加密,那样就可见行得通地巩固接口的安全性

越多关于加密算法的内容,可以去这里下载下Java加密与解密的格局提取密码:6j4v

参照他事他说加以考察文章:

App架构划虚拟计经验:接口的设计

总结

本篇作品差少之又少介绍了下写二个全体接口的具备手续和流程,具体的代码完毕能够参照作者开源出来的项目代码。

Java后台小项目开源地址

总结

那篇文章主要就讲了下团结在写那一个Java后台项目时用到了如何技术,看了怎么样小说,整理了下,希望对刚伊始上学Java后台开拓的人持有利于。笔者会在第三篇作品的时候,把本身写的那些大致的Java后台项目给开源出来,到时招待我们关注。

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图