## 1.前端页面展示层----V层
1.1ajax请求
1.1.1
表单操作,如新增、更新等涉及到比较多的表单参数,例子user.js的保存或更新用户信息方法
//判断是新增还是更新
var url = vm.user.userId == null ? "sys/user/save" : "sys/user/update";
$.ajax({
//规定请求的类型GET或POST
type: "POST",
//规定发送请求的 URL
url: baseURL + url,
//发送数据到服务器时所使用的内容类型,默认:"application/x-www-form-urlencoded"。
contentType: "application/json",
//发送到服务器的数据,这里根据具体的表单进行数据的获取,如从vue对象中获取
data: JSON.stringify(vm.xxx),
//服务器返回的数据类型,如果不指定,jQuery 将自动根据HTTP包MIME信息来智能判断
dataType:'json',
//请求成功回调函数
success: function(r){
if(r.code == 200){
alert('操作成功', function(){
//进行相关操作,如刷新列表的,关闭窗口等
});
}else{
alert(r.msg);
}
},
//请求失败回调函数
error : function(result) { alert(r.msg); }
});
1.1.2
比较少的参数,如更新数据状态 //创建对象 p = new Object(); p.id = "xxx";//获取到的参数值,可以通过jquery获取,也可以从vue对象中获取,根据情况而定 p.isEnable =
"yyy";//获取到的参数值,可以通过jquery获取,也可以从vue对象中获取,根据情况而定
//将请求参数JavaScript对象转换转换成JSON字符串格式 var dataStr = JSON.stringify(p);
$.ajax({
//请求方式 POST 或 GET type : "POST", //请求的后台url url : url, //发送信息至服务器时内容编码类型 contentType : "application/json",
//发送到服务器的数据 data : dataStr, //预期服务器返回的数据类型 dataType :
"json", //请求成功回调函数 success : function(result) { if(r.code ==
200){
alert('操作成功', function(){
//进行相关操作,如刷新列表的,关闭窗口等
});
}else{
alert(r.msg);
} }, //请求失败回调函数 error : function(result) { alert(r.msg); } });
1.2. JS common.js 前端自定义的公共js
1.3. vue、bootstrap、layui、layer等
************************************************************************
***************************** CONTROLLER *******************************
************************************************************************
## 2.Controller层----C层
2.0 controller类
@RestController
@RequestMapping("/sys/role")//url指向到模块级别即可,如后台管理中的角色模块/sys/role
public class SysUserController extends AbstractController
2.1 controller方法 @SysLog("保存用户")//操作日志,记录用户操作,新增、修改、删除、导入等关键操作要加
@RequiresPermissions("sys:user:save")//操作权限
@RequestMapping("/save")//url public ResultVo save(@RequestBody
SysUserVo userVo) {}
2.2 统一返回对象ResultVo
2.3 业务逻辑层要封装到service层,在controller只是做调用控制,真实明白controller在MVC中的角色
************************************************************************
******************************* SERVICE ********************************
************************************************************************
## 3.Service层----M层
3.1 数据校验
3.1.1 所有的数据校验,放在后台及service层进行校验,不要只在js里进行校验,数值类型例外
3.1.2 如果传到后台的是数值,后台VO中接收该值的字段数据类型也是数值(Long/Integer/Double等)
那么前端需要通过js校验该值是数值,否则如果是字母的话,会在传给后台解析时发生错误,如String不能转换成Long/Integer/Double
3.1.3 操作insert,update,delete数据库之前的数据校验,使用return返回校验结果,不要通过throw new BusinessException异常来做
3.1.4 在事务注解的方法中,保存/更新一个表的数据之后,再其他地方校验数据没有通过,要进行throw new BusinessException等抛异常
的方式进行返回,保证事务回滚。
3.1.5 不要使用异常机制做校验,这样会造成错误日志比较多,造成对错误日志的监控的干扰。如
try{
Integer.parseInt("123a")
}catch(Exception e){
return ResultVo.error("数据格式不正确");
}
3.1.6 校验示例
1.使用Validator进行数据是否位空等基本校验
ValidateMsgVo validateVo = ValidatorUtils.validateEntity(user, AddGroup.class); if (validateVo.getCode() !=
CommonConstant.SUCCESS_CODE) { return
ResultVo.error(validateVo.getMsg()); }
2.自己做业务、数据范围等校验
3.2 常需要关注及校验的地方
3.2.1 必填项校验,如果某个字段是必填项,则需要进行必填项校验,不能为空StringUtils,配合使用@NotBlank等Hibernate校验的注解
3.2.2 输入的字符串、输入的数值等长度校验,输入的字段的最小长度、最大长度的校验
3.2.3 唯一性校验,该字段在表里必须是唯一的,如用户名在用户表里要保持唯一性
3.2.4 值格式及范围校验,如手机号,邮箱通常的输入是有一个格式的,可以使用正则表达式校验,正则表达式工具类RegexValidateUtil
3.2.5 上传文件需要对格式、大小进行设置及校验
3.2.6 Tree型结构数据 (1)删除父级目录,子级目录必须为空!!! (2)更新节点父级目录,不能设置为父目录为自己的子级(环状结构)
3.2.7 与该模块有数据关联的功能模块,数据变更后模块间的校验,水平链路测试,如:角色权限变更后,拥有该角色的用户权限是否进行了相应的变更。
3.3 数据更新,正常情况下使用mybatis-plus提供的框架进行更新,不要自己写Update语句
3.3.0 mybatis-plus的查询、更新、插入的字段策略
FieldStrategy.IGNORED 忽略判断
FieldStrategy.NOT_NULL 默认策略,非NULL判断
FieldStrategy.NOT_EMPTY 非空判断,判断参数是""空字符串或者null
FieldStrategy.DEFAULT
FieldStrategy.NEVER
插入策略: FieldStrategy insertStrategy() default FieldStrategy.DEFAULT; 更新策略: FieldStrategy updateStrategy()
default FieldStrategy.DEFAULT; 查询策略: FieldStrategy whereStrategy()
default FieldStrategy.DEFAULT;
示例:
@TableField(updateStrategy = FieldStrategy.IGNORED) private String cardbinBankName;
3.3.1 正确更新方式一:使用mybatis-plus的UpdateWrapper进行指定列的更新,参考SysUserServiceImpl的restPwdByIds方法
正确例子:
public ResultVo restPwdByIds(SysUserVo userVo) {
// 封装需要改变的列和条件
UpdateWrapper<SysUserEntity> updateWrapper = new UpdateWrapper<>();
// 设置改变的条件即where
updateWrapper.lambda().eq(SysUserEntity::getUserId, userVo.getUserId());
//如果数据库表有版本号Version字段,则需要加上版本号作为条件,updateWrapper是不自动走乐观锁,需要手动增加该eq字段
updateWrapper.lambda().eq(SysUserEntity::getVersion,userVo.getVersion());
// 设置改变的列,开发人员要明确的知道要更新的具体列的!!!!!!
updateWrapper.lambda().set(SysUserEntity::getPassword, pwd);
updateWrapper.lambda().set(SysUserEntity::getUpdateUser, userName);
// 修改---这种方式可以将某个字段更新为null
boolean updateFlag = this.update(updateWrapper);
}
3.3.2 正确更新方式二:更新时,在service层根据ID查询出该Entity entity,然后给该Entity set欲更新的字段的新值entity.setXXX(newvalue),然后update(entity)。
正确例子:
public ResultVo update(EmergPayChannelVo emergPayChannelVo) {
//先根据主键到DB中查询出该条数据
EmergPayChannelEntity entity = this.getById(emergPayChannelVo.getId());
// 设置改变的列,开发人员要明确的知道要更新的具体列的!!!!!!
entity.setXXX1(emergPayChannelVo.getXXX1());
entity.setXXX2(emergPayChannelVo.getXXX2());
//更新---这种方式需要根据UpdateStrategy策略决定是否可以将某个字段进行更新为null,""
boolean updateFlag = this.updateById(entity);
}
3.3.3 错误更新示例:----这些方式绝对禁止的!!!!
不能使用dozer将vo转成对应的entity后,或者service层的入参就是entity,直接直接update()
错误例子:
public ResultVo update(EmergPayChannelEntity emergPayChannelEntity) {
this.updateById(emergPayChannelEntity);
}
public ResultVo update(EmergPayChannelVo emergPayChannelVo) {
EmergPayChannelEntity entity = dozerBeanConvertor.convertor(emergPayChannelVo,
EmergPayChannelEntity.class);
this.updateById(entity);
}
3.3.4 错误更新示例:----这些方式不推荐,如果实在需要这样写,需要报告相关开发负责人,基本上所有的都更都可以使用上述正确的更新方式
在xml文件中直接写SQL更新
<!-- 根据key,更新value --> <update id="updateValueByKey" parameterType="map"> update ibp_sys_config set param_value =
#{paramValue} where param_key = #{paramKey} </update>
3.3.5 update语句如果更新的记录的值没有变化,则返回的是false,这个需要注意,如
TableA表一条记录,
主键ID C列
1001 c
UPDATE TableA set C = c WHERE ID = 1001
3.4. mybatis-plus框架 之 MyMetaObjectHandler
在插入和更新数据时,mybatis-plus会自动填充更新一些列值,如CREATE_TIME,UPDATE_TIME,VERSION
需要在Entity的这些字段上加上该注解 //插入时填充 @TableField(fill = FieldFill.INSERT) //插入、更新时填充 @TableField(fill =
FieldFill.INSERT_UPDATE)
3.6. mybatis-plus框架之IService<T>
3.6.1 绝对不能出现两个service继承同一泛型的mybatis-plus的IService<T>,如
SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfigEntity> implements SysConfigService
SysAdLoginServiceImpl extends ServiceImpl<SysConfigMapper, SysConfigEntity> implements SysAdLoginService
否则会出现service注解的事务遇到异常后不回滚等怪异现象
3.7. 常用判断
3.7.1 枚举类型的值用枚举判断,如果系统里没有该枚举,则在ibp-common模块的enumtype文件夹下建相应枚举值, 如 sysSecretInfoEntity.getIsEnable() != IsNotEnum.IS.getValue()
3.7.2 equals判断,左边必须保证不为null,右边可以为null,如
CardTypeEnum.DEBIT_CARD.getName().equals(bankBinEntity.getType())
3.7.3 需要明白Long、long等存在对象类型和基本类型两种类型在判断相等时的区别,要转换成基本类型判断
3.8. VO、Entity(DO)、DTO转换
3.8.1 VO:前端页面与后台Java交互使用,VO中的字段比Entity的多,Entity中枚举值字段对应的名称,在VO中添加对应的名称,如
/**是否有效*/
private Integer isEnable;
/**是否有效*/
private Integer isEnable;
/**是否有效-显示名称*/
private Integer isEnableName;
3.8.2 Entity:java的DAO层与数据库的交互SysUserEntity,字段与DB中的列严格一对一,不能出现Entity中存在但是在DB中不存在的属性
3.8.3 DTO:系统对外提供接口数据,与外围系统交互
3.8.4 对象间转换使用DozerBeanConvertor进行转换(深度拷贝),示例
@Autowired private DozerBeanConvertor dozerBeanConvertor;
*************************************************************************
********************************** DAO **********************************
*************************************************************************
## 4. Dao层----数据访问层
4.0. mybatis-plus框架之 数据库主键
4.0.1 数据库建表时看《数据库表》文档里相关表的设计,建表时加上列注释、表注释、需要序列的增加序列
4.0.2 数据库主键策略
在mybatis-plus中使用oracle数据的情况下主键生成方式,目前使用了三种方式
4.1. 主键
1. 使用ORACLE数据库序列生成自增主键
1.1 Entity类级别加
@KeySequence(value = "SEQ_IBP_SYS_USER")
1.2 然后在ORACLE维护一个自增的序列,mysql因为有主键自增功能,在使用mysql数据库是可以直接使用mysql主键自增的特性
1.3 主键字段加
@TableId
private Long userId;
2. 使用mybatis-plus的@TableId默认值,雪花算法生成一个19位的数字,
在这种情况下主键(Long类型)返给前端显示的时候,由于js处理数据精度的问题,后面两位会变成0,因此需要对该字段进行转换,转换成字符串@JsonSerialize(using
= ToStringSerializer.class)
@TableId
//该注解是加在跟前端交互的VO上,如果跟前端交互的是Entity(目前存在一些这样的不规范的情形),则需要加载Entity上 @JsonSerialize(using = ToStringSerializer.class) private Long
bankId;
3. 使用UUID,生成32位的无序唯一的ID 4af0543bb8d111e8a88d6028b57d0c2f @TableId(type = IdType.UUID)
private String busiConfigId;
4.2. BaseEntity、BaseVo
4.2.1 根据是否需要createTime、createUser、updateTime、updateUser、version决定是否继承BaseEntity与BaseVo
4.2.2 后续会考虑怎加BasePageQueryVo作为分页查询基础类
4.3. mapper的java类和xml命名一样,这样方便查找,如
PaychannelInfoMapper.java
PaychannelInfoMapper.xml
4.4. 数据库SQL规范
4.2.1 关键字、保留字用大写,其他小写
4.2.2 换行缩进使用2-4个空格,不要使用tab键 SELECT c1,c2 FROM ibp_sys_config
WHERE param_key = #{paramKey}
4.2.3 #{}与${}的区别 #{}传入值时,sql解析时,参数是带引号的, ${}传入值,sql解析时,参数是不带引号的,是用来做动态sql拼接的,会出现SQL注入的情况。
4.2.4 在业务关联查询时,根据枚举值得到相应的枚举名称时,不能再SQL中直接关联IBP_SYS_DICT表,而是将业务数据查询出来之后,循环获取相应的枚举名称
4.2.5 列表查询,需要根据情况指定一列或多列排序
*************************************************************************
********************************* UTILS *********************************
************************************************************************* common下的各种工具类 策略集合 com.cignacmb.common.strategy 幂等token
IdempotentTokenService
*************************************************************************
************************* CONSTANT/ENUM -技术负责人需熟悉 **********************
************************************************************************* CommonConstant.java SysConfigConstant.java RedisKeyConstant.java
*************************************************************************
**************************** REDIS -技术负责人需熟悉 **************************
*************************************************************************
*************************************************************************
***************************** AOP -技术负责人需熟悉 ****************************
************************************************************************* SysLogAspect.java 用户操作日志 CheckApiSignAspect.java 接口验签校验
*************************************************************************
**************************** CONFIG -技术负责人需熟悉 **************************
*************************************************************************
*************************************************************************
************************* CRYPTO加解密 -技术负责人需熟悉 **************************
*************************************************************************
*************************************************************************
*************************** MAIL邮件 -技术负责人需熟悉 ***************************
*************************************************************************
********************************** 其他 **********************************
11.表单组件参考 组件参考bootstrap https://www.runoob.com/bootstrap/bootstrap-forms.html
https://v3.bootcss.com/ 按钮图标参考
http://www.fontawesome.com.cn/faicons/
12.评审代码时,每个人要拿本记住自己需要修改的具体的哪些点,之后一个一个修改掉
1、单独提醒
1、@requirePermission 权限注解
2、request请求可被模拟 ,js前端校验不靠谱,service中需求全部校验一遍(后端校验不能少)
3、ValidateUtils校验框架(实体雷的注解校验)+定制化校验 = services校验
4、bean管理,Spring中IoC两种ApplicationContext加载Bean的配置
4.1 通过ClassPathXmlApplicationContext加载xml配置文件
4.2 AnnotationConfigApplicationContext类加载JavaClass File的配置文件
spring的bean初始化后,会加载bean。普通使用,两种方式都可以拿到bean,但是在独立Thread线程中
@autoWired可能拿不到,就需要第一种,例如
//这里需要注意junit的测试文件必须以Test结尾 否则 mvn clean test 的时候将没有办法获取到测试用例
public class emailServiceTest {
//这里初始化一个本地的greenmail作为测试邮箱服务器
private GreenMail mail;
@Before
public void startMailBox(){
mail = new GreenMail(new ServerSetup(12000,null,"smtp"));
mail.setUser("[email protected]", "123456");
mail.start();
}
@Test
public void sendMail() throws MessagingException {
ApplicationContext context = new ClassPathXmlApplicationContext("account-service.xml");
// 这里将初始化发送邮件的service,而service所依赖的javamailsender初始化所配置的邮箱服务器和start中 定义的port、protocol也是一样的。 在测试test/resources中定义。这里greenmail起到模拟gmail服务器的能力
emailService service = (emailService)context.getBean("accountService");
service.sendMail("[email protected]","Test Subject","test
");
mail.waitForIncomingEmail(2000,1);
Message[] msgs = mail.getReceivedMessages();
Assert.assertEquals(1,msgs.length);
}
@After
public void closeMail(){
mail.stop();
}
}