项目开发经验规范总结-时刻更新
1、常用规范类
1.1、异常相关
1.1.1、业务异常类
package com.healerjean.proj.exception;
import com.healerjean.proj.enums.ResponseEnum;
/**
* 系统业务异常
*/
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(int code) {
this.code = code;
}
public BusinessException(String message) {
super(message);
this.code = ResponseEnum.逻辑错误.code;
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(ResponseEnum responseEnum) {
super(responseEnum.msg);
this.code = responseEnum.code ;
}
public BusinessException(ResponseEnum responseEnum,String message) {
super(message);
this.code = responseEnum.code ;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
this.code = ResponseEnum.逻辑错误.code;
}
public BusinessException(int code ,Throwable e) {
super(e);
this.code = code;
}
public BusinessException(ResponseEnum responseEnum, Throwable t) {
super(responseEnum.msg, t);
this.code = responseEnum.code;
}
public void setCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
1.1.2、参数异常类
package com.healerjean.proj.exception;
import com.healerjean.proj.enums.ResponseEnum;
/**
* @author HealerJean
* @ClassName ParameterErrorException
* @date 2019/10/17 16:19.
* @Description 参数错误
*/
public class ParameterErrorException extends com.healerjean.proj.exception.BusinessException {
public ParameterErrorException() {
super(ResponseEnum.参数错误);
}
public ParameterErrorException(ResponseEnum responseEnum) {
super(ResponseEnum.参数错误, responseEnum.msg);
}
public ParameterErrorException(String msg) {
super(ResponseEnum.参数错误, msg);
}
}
1.1.3、接口异常类
package com.healerjean.proj.exception;
import com.healerjean.proj.enums.ResponseEnum;
/**
* @author HealerJean
* @ClassName HaoDanKuApiException
* @date 2019/10/15 20:08.
* @Description
*/
public class HaoDanKuApiException extends BusinessException {
public HaoDanKuApiException( ) {
super(ResponseEnum.好单库口请求异常);
}
public HaoDanKuApiException(String msg) {
super(ResponseEnum.好单库接口数据异常, msg);
}
public HaoDanKuApiException(Throwable e) {
super(ResponseEnum.好单库口请求异常, e);
}
}
1.1.4、异常全局处理
package com.healerjean.proj.config;
import com.healerjean.proj.dto.ResponseBean;
import com.healerjean.proj.enums.ResponseEnum;
import com.healerjean.proj.exception.BusinessException;
import com.healerjean.proj.exception.ParameterErrorException;
import com.healerjean.proj.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.servlet.http.HttpServletResponse;
import javax.validation.UnexpectedTypeException;
import java.util.HashMap;
import java.util.Map;
/**
* @author HealerJean
* @version 1.0v
* @ClassName ControllerHandleExceptionConfig
* @date 2019/5/31 20:19.
* @Description
*/
@Slf4j
@ControllerAdvice
public class ControllerHandleConfig {
/**
* 不支持的请求方始
*/
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
@ResponseStatus(value = HttpStatus.METHOD_NOT_ALLOWED)
public ResponseBean methodNotSupportExceptionHandler(HttpRequestMethodNotSupportedException e) {
log.error("不支持的请求方式", e);
return ResponseBean.buildFailure(ResponseEnum.不支持的请求方式.code, e.getMessage());
}
/**
* 参数类型错误
* 1、(BindException : 比如 Integer 传入String )
* Field error in object 'demoDTO' on field 'age': rejected value [fasdf]; codes [typeMismatch.demoDTO.age,typeMismatch.age,typeMismatch.java.lang.Integer,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [demoDTO.age,age]; arguments []; default message [age]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Integer' for property 'age'; nested exception is java.lang.NumberFormatException: For input string: "fasdf"]
*/
@ExceptionHandler(value = {BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ResponseBean bindExceptionHandler(BindException e) {
log.error("====参数类型错误===", e);
return ResponseBean.buildFailure(ResponseEnum.参数类型错误.code, e.getMessage());
}
/**
* 参数格式问题
*/
@ExceptionHandler(value = {MethodArgumentTypeMismatchException.class, HttpMessageConversionException.class, UnexpectedTypeException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ResponseBean httpMessageConversionExceptionHandler(Exception e) {
log.error("====参数格式异常===", e);
return ResponseBean.buildFailure(ResponseEnum.参数格式异常.code, e.getMessage());
}
/**
* 参数错误
*/
@ExceptionHandler(value = ParameterErrorException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ResponseBean parameterErrorExceptionHandler(ParameterErrorException e) {
log.error("参数异常------------参数错误:code:{},message:{}", e.getCode(), e.getMessage());
return ResponseBean.buildFailure(e.getCode(), e.getMessage());
}
/**
* 业务异常,给前台返回异常数据
*/
@ExceptionHandler(value = BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ResponseBean businessExceptionHandler(BusinessException e) {
log.error("业务异常------------异常信息:code:{},message{}" ,e.getCode(), e.getMessage());
return ResponseBean.buildFailure(e.getCode(),e.getMessage());
}
/**
* 所有异常报错
*/
@ExceptionHandler
@ResponseBody
public HttpEntity<ResponseBean> allExceptionHandler(HttpServletResponse response, Exception e) {
log.error("====系统错误===", e);
response.setStatus(ResponseEnum.系统错误.code);
return returnMessage(ResponseBean.buildFailure(ResponseEnum.系统错误));
}
private HttpEntity<ResponseBean> returnMessage(ResponseBean responseBean) {
HttpHeaders header = new HttpHeaders();
header.add("Content-Type", "application/json");
header.add("Charset", "UTF-8");
return new HttpEntity<>(responseBean, header);
}
/**
* 参数非法
* 1、(BindException : 比如 Integer 传入abc )
*/
// @ExceptionHandler(value = {MethodArgumentTypeMismatchException.class, HttpRequestMethodNotSupportedException.class, HttpMessageConversionException.class, BindException.class, UnexpectedTypeException.class})
// @ResponseBody
// public HttpEntity<ResponseBean> httpMessageConversionExceptionHandler(HttpServletResponse response, Exception e) {
// log.error("====参数格式异常===", e);
// // 等同于 @ResponseStatus(HttpStatus.BAD_REQUEST)
// // 但是setStatus 不能比随便设置,最好一般情况下不要和HttpStatus 有重复的,这样有可能会造成没有输出Response body
// response.setStatus(ResponseEnum.参数格式异常.code);
// return returnMessage(ResponseBean.buildFailure(ResponseEnum.参数格式异常));
// }
// @ExceptionHandler(value ={HttpMessageConversionException.class, BindException.class} )
// @ResponseBody
// public HttpEntity<ResponseBean> httpMessageConversionExceptionHandler(Exception e) {
// log.error("====参数格式异常===", e);
// return new ResponseEntity<>(ResponseBean.buildFailure(ResponseEnum.参数格式异常),HttpStatus.BAD_REQUEST);
// }
}
1.2、枚举
1.2.1、响应枚举
package com.healerjean.proj.enums;
import java.util.Arrays;
import java.util.List;
/**
* @author HealerJean
* @version 1.0v
* @ClassName ResponseEnum
* @date 2019/6/13 20:45.
* @msgcription
*/
public enum ResponseEnum {
正常(200, "访问正常"),
参数错误(301, "参数错误"),
参数格式异常(302, "参数格式异常"),
不支持的请求方式(303, "不支持的请求方式"),
参数类型错误(304, "参数类型错误"),
逻辑错误(305, "逻辑错误"),
未登陆(306, "未登陆"),
登陆成功(307, "登陆成功"),
重复操作(308, "重复操作"),
非法操作(309, "非法操作"),
请求无法被服务器理解(400, "请求无法被服务器理解"),
未授权(401, "未授权"),
访问禁止(403, "访问禁止"),
页面丢失(404, "页面丢失"),
系统错误(500, "系统错误"),
未知错误(999, "未知错误"),
用户已经存在(1000, "用户已经存在"),
用户不存在(1001, "用户不存在"),
微信接口请求异常(2001,"微信接口请求异常"),
淘宝接口请求异常(2002,"淘宝接口请求异常"),
淘宝接口数据异常(2003,"淘宝接口数据异常"),
好单库口请求异常(2004,"好单库口请求异常"),
好单库接口数据异常(2005,"好单库接口数据异常"),
;
public int code;
public String msg;
ResponseEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static boolean checkExist( Integer code){
for (ResponseEnum value : ResponseEnum.values()){
if (value.code == code){
return true;
}
}
return false;
}
public static ResponseEnum toEnum(int code){
for (ResponseEnum value : ResponseEnum.values()){
if (value.code == code){
return value;
}
}
return ResponseEnum.未知错误;
}
public static String getMsg(int code){
for (ResponseEnum value : ResponseEnum.values()){
if (value.code == code){
return value.msg;
}
}
return ResponseEnum.未知错误.msg;
}
public ResponseEnum value(String enumName){
return valueOf( enumName ) ;
}
public static List<ResponseEnum> getList(){
return Arrays.asList(values());
}
}
1.2.2、业务枚举
package com.healerjean.proj.enums;
/**
* @author HealerJean
* @ClassName BusinessEnum
* @date 2019/9/30 14:39.
* @Description
*/
public interface BusinessEnum {
/**
* 验证码枚举
*/
enum VerifyCodeTypeEnum {
图片验证码("captcha", "图片验证码"),
注册邮箱验证码("RegistEmail", "注册邮箱验证码"),
找回密码邮箱验证码("RetrievePasswordEmail", "找回密码邮箱验证码"),
;
VerifyCodeTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String code;
public String desc;
public static VerifyCodeTypeEnum toEnum(String code) {
for (VerifyCodeTypeEnum item : VerifyCodeTypeEnum.values()) {
if (item.code.equals(code)) {
return item;
}
}
return null;
}
}
/**
* 模板类型
*/
enum TemplateTypeEnum {
邮件("Email", "邮件"),
;
public String code;
public String desc;
TemplateTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
/**
* 模板名字
*/
enum TempleNameEnum {
邮箱验证("VerifyEmail", "邮箱验证"),
找回密码邮箱验证("PasswordVerifyEmail", "找回密码邮箱验证"),
手机号验证("VerifyPhone", "手机号验证"),
;
TempleNameEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String code;
public String desc;
public static TempleNameEnum toEnum(String code) {
for (TempleNameEnum item : TempleNameEnum.values()) {
if (item.code.equals(code)) {
return item;
}
}
return null;
}
}
/**
* 菜单类型
*/
enum MenuTypeEnum {
后端菜单("0", "后端菜单"),
前端菜单("1", "前端菜单");
MenuTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String code;
public String desc;
public static MenuTypeEnum toEnum(String code) {
for (MenuTypeEnum value : MenuTypeEnum.values()) {
if (value.code .equals( code)) {
return value;
}
}
return null;
}
}
/**
* 用户类型
*/
enum UserTypeEnum {
管理人员("manager", "管理人员"),
网站用户("webuser", "网站用户");
UserTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String code;
public String desc;
public static MenuTypeEnum toEnum(String code) {
for (MenuTypeEnum value : MenuTypeEnum.values()) {
if (value.code .equals( code)) {
return value;
}
}
return null;
}
}
}
1.2.3、状态枚举
package com.healerjean.proj.enums;
/**
* @Description
* @Author HealerJean
* @Date 2019-06-16 01:58.
*/
public enum StatusEnum {
生效("10", "生效"),
废弃("99", "废弃");
StatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String code;
public String desc;
}
1.2.4、下拉菜单枚举
package com.duodian.admore.data;
import java.io.Serializable;
/**
* 下拉列表用
*/
public class LabelValueBean implements Serializable{
private static final long serialVersionUID = -1211726511402154326L;
private String label;
private String value;
private Boolean checked = false;
public LabelValueBean() {
}
public LabelValueBean(String label, String value) {
this.label = label;
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public Boolean getChecked() {
return checked;
}
public void setChecked(Boolean checked) {
this.checked = checked;
}
}
1.3、响应Bean
package com.healerjean.proj.dto;
import com.healerjean.proj.enums.ResponseEnum;
import com.healerjean.proj.utils.JsonUtils;
/**
* 返回对象
*/
public class ResponseBean {
private ResponseBean() {
}
public static ResponseBean buildSuccess() {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(true);
responseBean.setCode(ResponseEnum.正常.code);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
public static ResponseBean buildSuccess(String msg) {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(true);
responseBean.setCode(ResponseEnum.正常.code);
responseBean.setResult(msg);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
public static ResponseBean buildSuccess(Object result) {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(true);
responseBean.setCode(ResponseEnum.正常.code);
responseBean.setResult(result);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
public static ResponseBean buildSuccess(String msg, Object result) {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(true);
responseBean.setCode(ResponseEnum.正常.code);
responseBean.setMsg(msg);
responseBean.setResult(result);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
public static String buildSensitivitySuccess(String msg, Object result) {
return JsonUtils.toJsonStringWithSensitivity(buildSuccess(msg, result));
}
public static String buildSensitivitySuccess(Object result) {
return JsonUtils.toJsonStringWithSensitivity(buildSuccess(result));
}
public static ResponseBean buildFailure() {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(false);
responseBean.setCode(ResponseEnum.系统错误.code);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
public static ResponseBean buildFailure(String msg) {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(false);
responseBean.setCode(ResponseEnum.系统错误.code);
responseBean.setMsg(msg);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
public static ResponseBean buildFailure(ResponseEnum responseEnum) {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(false);
responseBean.setCode(responseEnum.code);
responseBean.setMsg(responseEnum.msg);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
public static ResponseBean buildFailure(int code, String msg) {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(false);
responseBean.setCode(code);
responseBean.setMsg(msg);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
public static ResponseBean buildFailure(ResponseEnum responseEnum, String msg) {
ResponseBean responseBean = new ResponseBean();
responseBean.setSuccess(false);
responseBean.setCode(responseEnum.code);
responseBean.setMsg(msg);
responseBean.setDate(System.currentTimeMillis() + "");
return responseBean;
}
private boolean success;
private Object result = "{}";
private String msg = "";
private int code;
private String date;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
2、代码规范
2.1、方法、实体名字:
小米
manager dao 层
queryBeanSingle
queryBeanPage
queryBeanList
queryBeanPageLike
queryBeanListLike
2.1.1、查询
- 单个记录查询
根据某几个简单属性查询
查询数据库 findBeanNameBy
不查询数据库 getBeanNameBy
查询数据库 findBeanName
不查询数据库 getBeanName
- 多个记录查询
根据某几个简单属性查询
查询数据库 qureyBeanNameBy
不查询数据库 dataBeanNameBy
查询数据库分页 qureyBeanNamePage
查询数据库集合 qureyBeanNameList
不查询数据库分页 dataBeanNamePage
不查询数据库集合 dataBeanNameList
2.1.2、删除
更新status字段
deleteBean
直接删除库里的数据
deleteDBeanName
2.1.4、接口请求 rquest/response
请求data
请求参数的data为 ReqRecordData
返回单个实体
返回结果的data为 RspRecordModel
返回集合
返回结果的data为 RspRecordList
2.2、注释、日志规范
2.2.1、controller日志
- -------8个-
- 头部是controller说明,日志内容写上头部以及功能日志说明
/**
* @author HealerJean
* @ClassName UserController
* @date 2019/10/18 14:10.
* @Description 用户管理
*/
public class UserController extends BaseController {
public ResponseBean register(@RequestBody UserDTO userDTO) {
log.info("用户管理---------用户注册---------请求参数:{}", userDTO);
log.info("用户管理---------用户注册---------请求参数:{},响应结果:{}",userDTO, userDTO);
}
/**
* @author HealerJean
* @ClassName UserController
* @date 2019/10/18 14:10.
* @Description 系统管理-字典管理
*/
public ResponseBean getDictType(@PathVariable Long id) {
log.info("系统管理-字典管理--------字典类型查询--------字典类型Id:{}", id);
log.info("系统管理-字典管理--------字典类型查询--------字典类型Id:{},响应结果:{}", id,dict);
}
2.2.2、 方法注释和日志
/**
* 根据合同模板生成合同初始pdf文件,讲合同状态设置为待确认状态
* 1、数据校验
* 1.1、基本数据校验
* 1.2、校验合同模板是否存在
* 1.3、校验签署人是否完整
* 1.4、校验签署方是否真实有效
* 2、把合同签署各方的信息和模板取出,将变量更替,生成word和pdf
* 4、保存签署人信息
* 5、合同初始化日志保存
* 6、删除临时文件
*/
void createContractByTeamplate(ContractDTO contractDTO) ;
2.3、异常问题
2.3.1 、工具类异常问题
public class UrlEncodeUtil {
public String encode(String text){
try {
return URLEncoder.encode(text,"gbk" );
}catch (UnsupportedEncodingException e) {
throw new RuntimeException("{}加密失败", text,e);
}catch (Exception e){
throw new RuntimeException("{}加密失败”,text, e);
}
}
}
3、数据库规范
3.1、demo
3.1.1、建表语句
- 是否 nuLl 必须not null ,这样不方便建立索引,为了防止为null,我们可以给赋予初始值,今后建议 default ‘’,给一个空串,空串不占内存空间,NULL是占内存空间的
create table test
(
`id` bigint(20) unsigned not null auto_increment comment '主键',
uk_name bigint(20) unsigned not null comment 'uk',
idx_name bigint(20) unsigned not null comment 'idx',
ref_item_id bigint(20) unsigned not null comment 'item表主键',
status varchar(32) not null comment '产品状态 字典表 productstatus',
create_user bigint(20) unsigned null default 0 comment '创建人',
create_name varchar(64) null default '' comment '创建人名称',
create_time datetime not null default current_timestamp comment '创建时间',
update_user bigint(20) unsigned null default 0 comment '更新人',
update_name varchar(64) null default '' comment '更新人名称',
update_time datetime not null default current_timestamp on update current_timestamp comment '更新时间',
unique index uk_name (uk_name) using btree comment '唯一索引',
index idx_name (idx_name) using btree comment '索引',
primary key (`id`) using btree
) engine = innodb comment '测试表'
3.1.2、建表说明
类型 | 名称 | 长度 | 解释 |
---|---|---|---|
bigint | 主键 | bigint(20) | |
varchar | 地址 | varchar(128) | |
varchar | 状态 | varchar(8) | |
varchar | 附件,逗号相隔 | varchar(128) | |
varchar | 备注、描述 | varchar(128/256) | |
varchar | 手机号 | varchar(20) | |
varchar | 名字 | varchar(64) | |
varchar | 邮件 | varchar(64) | |
decimal | 金额 | decimal(20,0) | 以分为单位 |
decimal | 百分比 | decimal(7,6) | 80% 0.800000 |
int | 数字 | int(10) | |
tinyint | 布尔 | tinyint(1) |
3.2、基础规范
3.2.1、统一说明
-
MySQL字符集统一使用utf8,默认排序规则:utf8_general_ci
-
使用InnoDB存储引擎,默认事务隔离级别REPEATABLE-READ(可重复读)
-
不要使用MySQL存储过程,视图,触发器,Event, InnoDB外键约束
-
每个数据表都添加注释 comment, 每个字段也添加comment
-
不要在数据库中存储大图片或大文件,尽量使用简单的数据类型,避免使用blob和text类型
-
单表数据量控制在1000W行以内 ,采用合适的分库分表策略,例如十库百表
3.2.2、字段设计
- 表示状态字段(0-255)的使用TINYINT UNSINGED ; 0避免成为有效状态值,非负的数字类型字段,都添加上UNSINGED,
- 时间字段使用时间日期类型,避免使用字符串类型存储,日期使用DATE类型,年使用YEAR类型,日期时间可使用DATETIME和TIMESTAMP
- 字符串VARCHAR(N), 其中N表示字符个数,请尽量减少N的大小
- 字段都设置为NOT NULL, 为字段提供默认值,如’’和’0’ ;
- 主键尽量保持增长趋势,建议使用id的生成器,避免使用表的自增列
3.2.3、sql使用规范
- 避免使用join,子查询等SQL
1.对于mysql,不推荐使用子查询和join是因为本身join的效率就是硬伤,一旦数据量很大效率就很难保证,强烈推荐分别根据索引单表取数据,然后在程序里面做join,merge数据,导致性能下降
2.子查询就更别用了,效率太差,执行子查询时,MYSQL需要创建临时表,查询完毕后再删除这些临时表,所以,子查询的速度会受到一定的影响,这里多了一个创建和销毁临时表的过程。
3.如果是JOIN的话,它是走嵌套查询的。小表驱动大表,且通过索引字段进行关联。如果表记录比较少的话,还是OK的。大的话业务逻辑中可以控制处理
- 在线业务的update和delete的where中是唯一索引或者主键,避免一次修改多条语句的情况,而且这样锁住的是一行数据
- 避免在MySQL数据库中进行计算操作,尽量由业务来处理运算,数据库,就应该让它做存储数据,查询数据的事情,
- 避免使用select * , 只返回自己需要的字段,枚举出要返回的字段名称
- SQL过滤的where条件尽量不使用OR, NOT IN , NOT EXIST
- 使用where IN()过滤时,IN集合个数必须小于500,因为in的数据少的时候,mysql优化器会可能会使用索引,但是当数据太多以后就不一定了,可以让MySQL按照ID顺序进行查询,这可能比随机的关联要更高效
3.3、表名设计
尽量使用 项目名(scf)_模块名_表名
项目名,因为我们可能一个数据库对应多个项目,这样容易区分
模块名,能够清晰明了的知道是哪个模块的表
3.4、字段设计
使用下划线,不要使用大小写组合,原因自己理解吧,兄弟
3.4.1、长度说明
3.4.1.1、数字型
类型 | 字节 | 范围(有符号) | 范围(无符号) | 用途 |
---|---|---|---|---|
tintint | 1 | (-128,127) | (0,255) | 小整数值 |
smallint | 2 | (-32 768,32 767) | (0,65 535) | 大整数值 |
mediumint | 3 | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 |
int/integer | 4 | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 |
bigint | 8 | (-9 233 372 036 854 775 808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 |
float | 4 | (-3.402 823 466 E+38,1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度/浮点数值 |
double | 8 | (1.797 693 134 862 315 7 E+308,2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度/浮点数值 |
decimal | M>D,为M+2否则为D+2 | decimal(M,D)依赖于M和D的值 | 依赖于M和D的值 | 小数值 |
3.4.1.2、字符类型
类型 | 字节 | 用途 |
---|---|---|
char | 0-255字节 | 变长字符串 |
varchar | 0-255字节 | 变长字符串 |
tinyblog | 0-255字节 | 不超过 255 个字符的二进制字符串 |
tinytext | 0-255字节 | 短文本字符串 |
blog | 0-65 535字节 | 二进制形式的长文本数据 |
text | 0-65 535字节 | 长文本数据 |
mediumblog | 0-16 777 215字节 16M | 二进制形式的中等长度文本数据 |
mediumtext | 0-16 777 215字节 16M | 中等长度文本数据 |
longblog | 0-4 294 967 295字节 4G | 二进制形式的极大文本数据 |
longtext | 0-4 294 967 295字节 4G | 极大文本数据 |
3.4.1.3、时间类型
类型 | 字节 | 取值范围 | 用途 | |
---|---|---|---|---|
year | 1 | 1901——2155 | YYYY | 日期值 |
date | 4 | 1000-01-01——9999-12-31 | YYYY-MM-DD | 时间值或持续时间 |
time | 3 | -838:59:59——838:59:59 | HH:MM:SS | 年份值 |
datetime | 8 | 1000-01-01 00:00:00——9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 |
timestamp | 4 | 19700101080001——20380119111407 | YYYYMMDD HHMMSSsss | 混合日期和时间值,时间戳 |
3.4.2、字段详解
3.4.2.1、字符 char(M),varcahr(M)
char是一种固定长度的类型,varchar则是一种可变长度的类型,它们的区别是:
-
char(M)类型的数据列里,每个值都占用M个字节,如果某个长度小于M,
MySQL就会在它的右边用空格字符补足
.(在检索操作中那些填补出来的空格字符将被去掉) -
varchar(M)类型的数据列里,每个值只占用刚好够用的字节再加上一个用来记录其长度的字节(即总长度为L+1字节)
注意:一般千万不要使用text ,这样从mybatis取出来看似是String类型的,但是在实际使用中却或出现字符问题
3.4.2.3、日期
1、Local类型的日期
LocalTime 对应 **time **只包括时间
LocalDate 对应 **date ** 只包括日期
LocalDateTime 对应 **timestamp datetime **包括日期和时间
2、timestamp
timestamp 多个日期,如果可能为空,则建议使用datetime(默认值建议设置为)
0001-01-01 00:00:00,因为0000-00-00 00:00:00mysql不能保存,而且会报错,
普通字段不要设置为timestamp,timestamp列必须有默认值,默认值可以为“0000-00-00 00:00:00”,但不能为null。如果我们在save实体的时候,没有给相关timestamp设置值,那么他就会自动由mysql将当前时间设置进去
3、注解使用
@Column(insertable = true,updatable = false)
@ApiModelProperty(hidden = true)
private Date cdate;
@UpdateTimestamp
@ApiModelProperty(hidden = true)
private Date udate;
3.4.2.3、小数设计 decimal
mysql中的decimal字段,声明语法为DECIMAL(M,D) D是小数点右侧数位0-30,M最大精度数位,1-65
-
D:是小数部分的位数,若插入的值未指定小数部分或者小数部分不足D位则会自动补到D位小数,若插入的值小数部分超过了D为则会发生截断,截取前D位小数(四舍五入截取)。
-
M:是整数部分+小数部分=总长度,也即插入的数字整数部分不能超过M-D位,否则不能成功插入,会报超出范围的错误。
规则:先保证小数点,再保证整数,
举例说明,11615.23653234568这个数存你说的三个格式
decimal:11615
decimal(3):999
decimal(3,2):9.99
decimal(10,5)11615.23653
超出精度范围的数会被强制进位并只显示数据类型定义的格式
3.4.2.4、数字
1、mysql 类型有符号范围和无符号范围
带符号和无符号,顾名思义,就是是否有正负之分:
比如8位的二进制,
如果带符号,需要用1位表示符号(1表示负数,0表示正),剩下7位表示数据.
那么表示范围是-128—127(包括-0和+0).如果不带符号,8位全部表示数据,
那么表示范围是 0–256最小负数二进制是1000 0000 → 减一: 0111 1111 取反: 1000 0000 = 128 所以应该为 - 128
最大负数二进制是1111 1111 → 减一: 1111 1110 取反: 0000 0001 = 1 所以应该为 - 1
如果带符号,需要用1位表示符号(1表示负数,0表示正),剩下7位表示数据. 那么表示范围是-128—127(包括-0和+0).理解下了的话,就是无符号都是正数 ,所以主键自增Id我们一般都设计为无符号的
`id` bigint(16) unsigned NOT NULL AUTO_INCREMENT,
2、各个数据类型的长度以及默认
整型的每一种都分无符号(unsigned)和有符号(signed)两种类型,在默认情况整型变量都是有符号的类型
3、int(M) (用于提示开发者长度)
这个长度M
并不代表允许存储的宽度,int(M)
,也就是int(3
)和int(11)
能够存储的数据是一样的
只有联合zerofill参数才能有意义,否则int(3)
和int(11)
没有任何区别。
- 不加zeroffill没有区别
create table test_int
(
id int(3) unsigned not null,
uid int(11) unsigned not null,
uuid int unsigned not null
);
#插入数据
insert into test_int
values (4294967295, 4294967295, 4294967295);
#查询数据,发现没有什么区别
select * from test_int;
+------------+------------+------------+
| id | uid | uuid |
+------------+------------+------------+
| 4294967295 | 4294967295 | 4294967295 |
+------------+------------+------------+
1 row in set (0.00 sec)
- 有了zeroffill 不足会自动补0
create table test_int1
(
id int(3) unsigned zerofill not null,
uid int(11) unsigned zerofill not null,
uuid int unsigned zerofill not null
);
#插入数据
insert into test_int1
values (4294967295, 4294967295, 4294967295);
insert into test_int1
values (1, 4294967295, 110000);
#查询数据 发现前面的不足长度的右0了,当然不能使用idea测试,idea没有显示0
mysql> select * from test_int1;
+------------+-------------+------------+
| id | uid | uuid |
+------------+-------------+------------+
| 4294967295 | 04294967295 | 4294967295 |
| 001 | 04294967295 | 0000110000 |
+------------+-------------+------------+
2 rows in set (0.02 sec)
- 当使用zerofill 时,默认会自动加unsigned(无符号),zerofill默认为int(10)
create table test_int2
(
id int(3) unsigned zerofill not null,
uid int zerofill not null,
uuid int unsigned zerofill not null
);
# 下面的不能执行成功,以为无符号的都是正数
insert into test_int2
values (1, -4294967295, 110000);
insert into test_int2
values (1, 12345678, 110000);
mysql> select * from test_int2;
+-----+------------+------------+
| id | uid | uuid |
+-----+------------+------------+
| 001 | 0012345678 | 0000110000 |
+-----+------------+------------+
3.4.2.5、boolean
boolean值用1代表TRUE,0代表FALSE。boolean在mysql里的类型为tinyint(1)。mysql里有四个常量:true,false,TRUE,FALSE分别代表1,0,1,0。
private Boolean loan;
tinyint(1) NOT NULL COMMENT '是否借款 true/false 1/0',
3.5、索引设计
- 普通索引前缀:idx_索引字段名,唯一索引前缀:uk_索引字段名
- 每个表必须显示指定主键,主键尽量为一个字段,且为数字类型,避免使用字符串
- 主键尽量保持增长趋势,建议使用id的生成器,而不使用数据库自增(这个很难,我这里还是自增的)
- 重要的SQL或调用频率高的SQL
update/select/delete的where条件列字段都要添加索引
order by , group by, distinct的字段都要添加索引
- 组合索引创建时,把区分度(选择性)高的字段放在前面;根据SQL的特性,调整组合索引的顺序
- 禁止对索引列进行函数运算和数学运算
- 每个表的索引个数尽量少于5个,避免创建重复冗余索引;每个组合索引尽量避免超过3个字段
**唯一索引添加之后,如果是逻辑删除的,如果有可能恢复,记得还原id,没有添加唯一索引,则按照正常的删除即可 **
**不过一般情况下,我们这种下面这种没有业务的是可以恢复的,如果是设计到用户名,一般情况下我个人的理解是注销的用户,数据进行迁移,当前数据库中没有,然后就可以恢复了 **
1、没有索引删除的
/**
* 添加字典类型
*
* @return
*/
@Override
public void addDictType(DictionaryTypeDTO typeDTO, LoginUserDTO loginUserDTO) {
SysDictionaryTypeQuery query = new SysDictionaryTypeQuery();
query.setTypeKey(typeDTO.getTypeKey());
query.setStatus(StatusEnum.生效.code);
SysDictionaryType type = sysDictionaryTypeManager.findByQueryContion(query);
if (type != null) {
throw new BusinessException(ResponseEnum.字典类型已存在);
}
type = new SysDictionaryType();
type.setCreateUser(loginUserDTO.getUserId());
type.setCreateName(loginUserDTO.getRealName());
type.setTypeKey(typeDTO.getTypeKey());
type.setTypeDesc(typeDTO.getTypeDesc());
type.setStatus(StatusEnum.生效.code);
type.setUpdateUser(loginUserDTO.getUserId());
type.setUpdateName(loginUserDTO.getRealName());
sysDictionaryTypeManager.insertSelective(type);
}
/**
* 删除字典类型
*/
@Override
public void deleteDictType(Long id, LoginUserDTO loginUserDTO) {
SysDictionaryType type = sysDictionaryTypeManager.findById(id);
if (type == null) {
throw new BusinessException(ResponseEnum.字典类型不存在);
}
type.setStatus(StatusEnum.废弃.code);
type.setUpdateUser(loginUserDTO.getUserId());
type.setUpdateName(loginUserDTO.getRealName());
sysDictionaryTypeManager.updateSelective(type);
}
/**
* 修改字典类型
*/
@Override
public void updateDictType(DictionaryTypeDTO typeDTO, LoginUserDTO loginUserDTO) {
SysDictionaryTypeQuery query = new SysDictionaryTypeQuery();
query.setTypeKey(typeDTO.getTypeKey());
query.setStatus(StatusEnum.生效.code);
SysDictionaryType typeExist = sysDictionaryTypeManager.findByQueryContion(query);
//判断是是否已经存在数据
if (typeExist != null && !typeExist.getId().equals(typeDTO.getId()) ) {
throw new BusinessException(ResponseEnum.字典类型已存在);
}
SysDictionaryType type = BeanUtils.dtoToDictionaryType(typeDTO);
type.setUpdateUser(loginUserDTO.getUserId());
type.setUpdateName(loginUserDTO.getRealName());
sysDictionaryTypeManager.updateSelective(type);
}
2、有索引删除的代码
/**
* 添加域名
*/
@Override
public DomainDTO addDomain(DomainDTO domainDTO, LoginUserDTO loginUserDTO) {
AlimamaInfoDTO alimamaInfoDTO = loginUserDTO.getAlimamaInfo();
SysDomainQuery domainQuery = new SysDomainQuery();
domainQuery.setRefAlimamaInfoId(alimamaInfoDTO.getAlimamaInfoId());
domainQuery.setType(domainDTO.getType());
SysDomain domain = sysDomainManager.findByQueryContion(domainQuery);
if (domain != null) {
if (domain.getStatus().equals(StatusEnum.生效.code)) {
throw new BusinessException(ResponseEnum.域名已存在);
} else {
domain.setRefAlimamaInfoId(alimamaInfoDTO.getAlimamaInfoId());
domain.setStatus(StatusEnum.生效.code);
domain.setValue(domainDTO.getValue());
domain.setCreateUser(loginUserDTO.getUserId());
domain.setCreateName(loginUserDTO.getRealName());
domain.setUpdateUser(loginUserDTO.getUserId());
domain.setUpdateName(loginUserDTO.getRealName());
sysDomainManager.updateSelective(domain);
}
} else {
domain = new SysDomain();
domain.setRefAlimamaInfoId(alimamaInfoDTO.getAlimamaInfoId());
domain.setType(domainDTO.getType());
domain.setValue(domainDTO.getValue());
domain.setStatus(StatusEnum.生效.code);
domain.setCreateUser(loginUserDTO.getUserId());
domain.setCreateName(loginUserDTO.getRealName());
domain.setUpdateUser(loginUserDTO.getUserId());
domain.setUpdateName(loginUserDTO.getRealName());
sysDomainManager.save(domain);
}
domainDTO.setDomainId(domain.getId());
return domainDTO;
}
/**
* 修改域名
*/
@Override
public void updateDomain(DomainDTO domainDTO, LoginUserDTO loginUserDTO) {
AlimamaInfoDTO alimamaInfoDTO = loginUserDTO.getAlimamaInfo();
SysDomainQuery domainQuery = new SysDomainQuery();
domainQuery.setRefAlimamaInfoId(alimamaInfoDTO.getAlimamaInfoId());
domainQuery.setType(domainDTO.getType());
SysDomain domain = sysDomainManager.findByQueryContion(domainQuery);
if(domain != null ){
if(!domain.getId().equals(domainDTO.getDomainId())){
if(domain.getStatus().equals(StatusEnum.生效.code)){
throw new BusinessException(ResponseEnum.域名已存在);
}else {//前提必须是status,否则会出问题
domain.setStatus(StatusEnum.生效.code);
domain.setValue(domainDTO.getValue());
domain.setUpdateUser(loginUserDTO.getUserId());
domain.setUpdateName(loginUserDTO.getRealName());
sysDomainManager.updateSelective(domain);
}
}else {
domain.setValue(domainDTO.getValue());
domain.setUpdateUser(loginUserDTO.getUserId());
domain.setUpdateName(loginUserDTO.getRealName());
sysDomainManager.updateSelective(domain);
}
}else {
throw new BusinessException(ResponseEnum.域名不存在);
}
}
/**
* 删除域名
*/
@Override
public void deleteDomain(DomainDTO domainDTO, LoginUserDTO loginUserDTO) {
AlimamaInfoDTO alimamaInfoDTO = loginUserDTO.getAlimamaInfo();
SysDomain domain = sysDomainManager.findByIdAndAlimamaId(domainDTO.getDomainId(), alimamaInfoDTO.getAlimamaInfoId());
if(domain == null ){
throw new BusinessException(ResponseEnum.域名不存在);
}
if(domain.getStatus().equals(StatusEnum.废弃.code)){
throw new BusinessException(ResponseEnum.重复操作);
}
domain.setStatus(StatusEnum.废弃.code);
domain.setUpdateUser(loginUserDTO.getUserId());
domain.setUpdateName(loginUserDTO.getRealName());
sysDomainManager.updateSelective(domain);
}
3.5、sql规范
select o.createTime,
o.clickTime,
c.itemTitle,
o.itemId,
o.orderNo,
c.shopTitle,
o.estimateAmount,
o.payAmount,
c.commissionRatio,
o.orderStatus
from user_order o left join
coupon_taoke_data c on c.id = o.taokeId
where 1=1
4、接口文档规范
4.1、多点
4.1.1、 查询排重接口
接口详情 | |
---|---|
地址 | http://www.baidu.com (正式环境) |
请求方式 | GET |
参数 | 是否必填 | 说明 |
---|---|---|
idfa | 是 | 广告标识符,只支持单个查询 |
source | 是 | 渠道来源,具体值在接入时再进行分配 |
返回结果 | 格式 | JSON |
---|---|---|
状态码 | 10000 | success(调用成功) |
10001 | param error(参数错误) | |
10002 | query failed(查询失败) | |
10010 | access prohibited(访问拒绝) |
具体返回结果举例:
1、查询成功
{
"state": 10000,
"message": "success",
"data": {
"BD239708-2874-417C-8292-7E335A537FAD": 1 //已经存在
}
}
{
"state": 10000,
"message": "success",
"data": {
"BD239708-2874-417C-8292-7E335A537FAD": 0 //不存在
}
}
- 接口调用失败
{
"state": 10010,
"message": "access prohibited",
"data": [
]
}
4.2、小米
4.2.1、角色列表查询
说明
- 测试调用地址:/api/roles
- 调用方式:GET
请求参数
参数名称 | 参数类型 | 参数长度 | 是否必需 | 说明 | 备注 |
---|---|---|---|---|---|
pageSize | 整数 | 否 | 每页显示数量 | 默认10 | |
pageNo | 整数 | 否 | 当前查看页码 | 默认1 | |
roleName | 字符串 | 64 | 否 | 角色名称 | |
systemCode | 字符串 | 32 | 否 | 系统CODE | |
isPage | 布尔 | 4 | 否 | 是否分页 | true/false |
请求报文样例
{
"pageSize": 1,
"pageNo": 1,
"roleName": "",
"systemCode": "scf-manager",
"isPage": true
}
响应参数
参数名称 | 参数类型 | 参数长度 | 是否必需 | 说明 | 备注 |
---|---|---|---|---|---|
msg | 字符串 | 255 | 是 | 返回结果 | |
total | 数字 | 否 | 总数 | ||
pageNo | 数字 | 否 | 页数 | ||
totalPage | 数字 | 否 | 总页数 | ||
pageSize | 数字 | 否 | 每页数量 | ||
datas | Role数组 | 否 | 返回的数据信息 |
Role 数据结构
参数名称 | 参数类型 | 参数长度 | 是否必需 | 说明 | 备注 |
---|---|---|---|---|---|
id | 数字 | 16 | 是 | id | |
roleName | 字符串 | 64 | 是 | 角色名称 | |
systemCode | 字符串 | 64 | 是 | 系统CODE | |
status | 字符串 | 8 | 是 | 状态 | |
desc | 字符串 | 255 | 否 | 描述 |
响应报文样例
{
"msg": "角色列表查询成功",
"total": 2,
"pageNo": 1,
"totalPage": 1,
"datas": [
{
"id": 1,
"roleName": "后台管理员",
"systemCode": "scf-manager",
"status": "10"
},
{
"id": 4,
"roleName": "测试角色哦",
"systemCode": "scf-manager",
"status": "10",
"desc": "真的是测试"
}
],
"pageSize": 10
}
返回码解析
返回码 | 含义 | 备注 |
---|---|---|
200 | 成功 |
感兴趣的,欢迎添加博主微信,
哈,博主很乐意和各路好友交流,如果满意,请打赏博主任意金额,感兴趣的在微信转账的时候,备注您的微信或者其他联系方式。添加博主微信哦。
请下方留言吧。可与博主*讨论哦
支付包 | 微信 | 微信公众号 |
---|---|---|