【培训一】开发避坑指南:规范开发

 

## 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(); } }

你可能感兴趣的:(mybatis,mysql,java)