Skill: shinelon/java-development-manual (version 0.1.0)

--- name: java-development-manual description: | Java开发手册规约集合，基于阿里巴巴Java开发手册（嵩山版）。 涵盖7大维度：编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要：(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设计 (7) 工程架构设计时使用此skill。 触发词：Java规范、阿里规约、代码规范、开发手册、编程规约、异常处理、单元测试、安全、MySQL、工程结构、设计模式。 --- # Java开发手册（嵩山版） ## 概述 本手册基于阿里巴巴Java开发手册（嵩山版），将规约分为7个维度。规约按约束力强弱分为： | 级别 | 含义 | 说明 | |------|------|------| | **【强制】** | 必须遵守 | 违反可能导致严重问题 | | **【推荐】** | 建议遵守 | 提升代码质量和可维护性 | | **【参考】** | 可选择性采纳 | 根据实际情况判断 | ## 章节导航 根据需求选择对应章节的详细规约： | 章节 | 适用场景 | 详细文档 | |------|---------|----------| | **编程规约** | 命名、格式、OOP、并发、集合处理 | [coding-convention.md](references/coding-convention.md) | | **异常日志** | 错误码、异常处理、日志规范 | [exception-log.md](references/exception-log.md) | | **单元测试** | 测试用例、覆盖率、Mock | [unit-test.md](references/unit-test.md) | | **安全规约** | SQL注入、XSS、CSRF、脱敏 | [security.md](references/security.md) | | **MySQL数据库** | 建表、索引、SQL、ORM | [mysql.md](references/mysql.md) | | **工程结构** | 分层架构、依赖管理、服务器 | [project-structure.md](references/project-structure.md) | | **设计规约** | UML、设计模式、设计原则 | [design.md](references/design.md) | ## 快速参考 ### 命名规范速查 ```java // 类名：UpperCamelCase public class UserService { } public class UserDO { } // DO/DTO/VO例外 // 方法名/变量：lowerCamelCase private String userName; public void getUserById() { } // 常量：全大写+下划线 public static final int MAX_RETRY_COUNT = 3; // 包名：全小写 package com.company.project.service; ``` ### 禁止事项速查 | 禁止 | 原因 | |------|------| | 拼音命名 | 可读性差 | | 魔法值 | 难以维护 | | `SELECT *` | 性能和可维护性 | | Executors创建线程池 | 可能OOM | | 字符串拼接SQL | 注入风险 | | finally中return | 丢失try返回值 | | foreach中remove | ConcurrentModificationException | ### 必须事项速查 | 必须 | 原因 | |------|------| | 覆写方法加@Override | 避免签名错误 | | 表必备三字段 | id, create_time, update_time | | 敏感数据脱敏 | 隐私保护 | | 参数校验 | 安全防护 | | ThreadLocal回收 | 避免内存泄漏 | | 日志用占位符 | 性能优化 | ### 异常处理速查 ```java // 正确的异常处理 try { // 业务逻辑 } catch (SpecificException e) { logger.error("操作失败, 参数: {}", params, e); throw new BusinessException("用户友好提示", e); } finally { // 资源关闭（JDK7+ try-with-resources） } ``` ### 数据库速查 ```sql -- 建表必备 CREATE TABLE example ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 索引命名 -- 主键: pk_字段名 -- 唯一: uk_字段名 -- 普通: idx_字段名 ``` ### 并发处理速查 ```java // 线程池创建 ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(queueCapacity), new ThreadFactory() { private AtomicInteger counter = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "worker-" + counter.getAndIncrement()); } }, new ThreadPoolExecutor.CallerRunsPolicy() ); // ThreadLocal使用 try { threadLocal.set(value); // 业务逻辑 } finally { threadLocal.remove(); // 必须回收 } ``` ## 使用指南 ### 代码审查场景 1. **命名检查** → 查看 [coding-convention.md](references/coding-convention.md) 的"命名风格"章节 2. **并发问题** → 查看 [coding-convention.md](references/coding-convention.md) 的"并发处理"章节 3. **异常处理** → 查看 [exception-log.md](references/exception-log.md) 4. **安全问题** → 查看 [security.md](references/security.md) ### 新项目搭建场景 1. **架构设计** → 查看 [design.md](references/design.md) 2. **分层结构** → 查看 [project-structure.md](references/project-structure.md) 3. **数据库设计** → 查看 [mysql.md](references/mysql.md) 4. **单元测试** → 查看 [unit-test.md](references/unit-test.md) ### 问题排查场景 1. **NPE问题** → 查看 [exception-log.md](references/exception-log.md) 的"NPE防护" 2. **性能问题** → 查看 [mysql.md](references/mysql.md) 的"索引规约" 3. **并发问题** → 查看 [coding-convention.md](references/coding-convention.md) 的"并发处理"

path: references/coding-convention.md
content: # 编程规约

## 目录
- [一、命名风格](#一命名风格)
- [二、常量定义](#二常量定义)
- [三、代码格式](#三代码格式)
- [四、OOP规约](#四oop规约)
- [五、日期时间](#五日期时间)
- [六、集合处理](#六集合处理)
- [七、并发处理](#七并发处理)
- [八、控制语句](#八控制语句)
- [九、注释规约](#九注释规约)
- [十、前后端规约](#十前后端规约)

---

## 一、命名风格

### 【强制】规约

1. **禁止特殊字符开头/结尾**：代码命名均不能以下划线或美元符号开始/结束
   - 反例：`_name` / `__name` / `$name` / `name_` / `name$` / `name__`

2. **禁止拼音混合命名**：严禁使用拼音与英文混合，更不允许直接使用中文
   - 正例：`ali` / `alibaba` / `taobao` / `hangzhou`（国际通用名称可视同英文）
   - 反例：`DaZhePromotion` / `getPingfenByName()` / `String fw` / `int 某变量 = 3`

3. **禁止歧视性词语**：代码和注释中避免使用种族歧视性词语
   - 正例：`blockList` / `allowList` / `secondary`
   - 反例：`blackList` / `whiteList` / `slave`

4. **类名使用UpperCamelCase**：例外情况：DO/BO/DTO/VO/AO/PO/UID等
   - 正例：`ForceCode` / `UserDO` / `HtmlDTO` / `XmlService`
   - 反例：`forcecode` / `UserDo` / `HTMLDto` / `XMLService`

5. **方法名、参数名、成员变量使用lowerCamelCase**
   - 正例：`localValue` / `getHttpMessage()` / `inputUserId`

6. **常量全大写，下划线分隔**
   - 正例：`MAX_STOCK_COUNT` / `CACHE_EXPIRED_TIME`
   - 反例：`MAX_COUNT` / `EXPIRED_TIME`

7. **特殊类命名**：
   - 抽象类：`Abstract`或`Base`开头
   - 异常类：`Exception`结尾
   - 测试类：被测试类名开始，`Test`结尾

8. **数组声明**：类型与中括号紧挨
   - 正例：`int[] arrayDemo`
   - 反例：`String args[]`

9. **POJO布尔变量不加is前缀**：避免框架解析序列化错误
   - 说明：数据库字段用`is_xxx`，需要在`<resultMap>`设置映射

10. **包名全小写，单数形式**：点分隔符间仅一个自然语义单词
    - 正例：`com.alibaba.ei.kunlun.aap.util`、类名`MessageUtils`

11. **避免子父类成员变量同名**

12. **杜绝不规范缩写**
    - 反例：`AbsClass` / `condi` / `Fu`

13. **接口方法不加修饰符**：保持简洁，加上Javadoc注释

14. **Service/DAO实现类用Impl后缀**
    - 正例：`CacheServiceImpl`实现`CacheService`

15. **枚举类带Enum后缀，成员全大写**
    - 正例：`ProcessStatusEnum.SUCCESS` / `ProcessStatusEnum.UNKNOWN_REASON`

### 【推荐】规约

- 使用完整单词组合表达（自解释）
- 类型名词放词尾：`startTime` / `workQueue` / `nameList`
- 设计模式体现在命名中：`OrderFactory` / `LoginProxy` / `ResourceObserver`

### 各层命名规约

| 层级 | 方法前缀 | 示例 |
|------|---------|------|
| 获取单个对象 | get | `getUserById` |
| 获取多个对象 | list | `listUsers` |
| 获取统计值 | count | `countUsers` |
| 插入 | save/insert | `saveUser` |
| 删除 | remove/delete | `deleteUser` |
| 修改 | update | `updateUser` |

**领域模型命名**：
- 数据对象：`xxxDO`（xxx为表名）
- 数据传输对象：`xxxDTO`
- 展示对象：`xxxVO`
- 禁止命名成`xxxPOJO`

---

## 二、常量定义

### 【强制】规约

1. **禁止魔法值**：不允许未经预先定义的常量直接出现
   ```java
   // 反例
   String key = "Id#taobao_" + tradeId;
   cache.put(key, value);
   ```

2. **Long类型使用大写L**
   ```java
   // 正例
   Long a = 2L;
   // 反例（容易混淆为数字21）
   Long a = 2l;
   ```

### 【推荐】规约

- 按功能归类常量：`CacheConsts` / `SystemConfigConsts`
- 常量复用层次：跨应用→应用内→子工程→包内→类内
- 固定范围变化用enum定义：
  ```java
  public enum SeasonEnum {
      SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
      private int seq;
      SeasonEnum(int seq) { this.seq = seq; }
      public int getSeq() { return seq; }
  }
  ```

---

## 三、代码格式

### 【强制】规约

1. **大括号规则**：
   - 空代码块：`{}`
   - 非空：左大括号前不换行，后换行；右大括号前换行

2. **空格规则**：
   - 小括号内外不加空格
   - `if/for/while/switch/do`与括号间加空格
   - 运算符左右加空格

3. **缩进**：4个空格，禁止Tab字符

4. **注释格式**：双斜线与内容间仅一个空格
   ```java
   // 这是正确的注释格式
   ```

5. **单行字符限制**：不超过120字符
   - 换行缩进4空格
   - 运算符与下文一起换行
   - 点符号与下文一起换行
   - 逗号后换行

6. **参数逗号后加空格**：
   ```java
   method(args1, args2, args3);
   ```

7. **IDE编码设置**：UTF-8，换行符使用Unix格式

### 【推荐】规约

- 单个方法总行数不超过80行
- 不同逻辑/业务代码间插入空行

---

## 四、OOP规约

### 【强制】规约

1. **静态成员通过类名访问**，不通过对象引用

2. **覆写方法必须加@Override注解**

3. **可变参数放最后**，同类型同含义才使用

4. **接口过时加@Deprecated注解**

5. **禁止使用过时类/方法**

6. **equals避免NPE**：用常量调用
   ```java
   // 正例
   "test".equals(object);
   // 推荐
   Objects.equals(a, b);
   ```

7. **整型包装类比较用equals**：Integer缓存范围-128~127

8. **货币金额用整型存储**：最小货币单位

9. **浮点数比较**：
   ```java
   // 方式1：误差范围
   float diff = 1e-6F;
   if (Math.abs(a - b) < diff) { ... }

   // 方式2：BigDecimal
   BigDecimal a = new BigDecimal("1.0");
   if (x.compareTo(y) == 0) { ... }
   ```

10. **BigDecimal比较用compareTo()**，不用equals()

11. **DO类属性类型匹配数据库字段类型**

12. **BigDecimal构造用String或valueOf**：
    ```java
    // 正例
    new BigDecimal("0.1");
    BigDecimal.valueOf(0.1);
    // 反例
    new BigDecimal(0.1F);  // 精度损失
    ```

13. **POJO属性用包装类型**，局部变量用基本类型

14. **POJO不设属性默认值**

15. **serialVersionUID不要随意修改**

16. **构造方法禁止业务逻辑**，放init方法

17. **POJO必须写toString()**，继承时加`super.toString()`

18. **禁止同时存在isXxx()和getXxx()**

### 【推荐】规约

- String的split结果做边界检查
- 重载方法放一起
- 方法顺序：公有>私有>getter/setter
- setter/getter不加业务逻辑
- 循环内字符串连接用StringBuilder
- 慎用Object.clone()
- 访问控制从严

---

## 五、日期时间

### 【强制】规约

1. **年份用小写y**：
   ```java
   // 正例
   new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
   // 说明：YYYY是week in which year
   ```

2. **区分大小写**：
   - 大写M：月份
   - 小写m：分钟
   - 大写H：24小时制
   - 小写h：12小时制

3. **获取毫秒数**：
   ```java
   // 正例
   System.currentTimeMillis();
   // 反例
   new Date().getTime();
   ```

4. **禁止使用**：`java.sql.Date` / `java.sql.Time` / `java.sql.Timestamp`

5. **禁止写死一年365天**：
   ```java
   // 正例
   int daysOfThisYear = LocalDate.now().lengthOfYear();
   ```

### 【推荐】规约

- 避免闰年2月问题
- 月份使用枚举：`Calendar.JANUARY`

---

## 六、集合处理

### 【强制】规约

1. **覆写equals必须覆写hashCode**

2. **判空用isEmpty()**，不用size()==0

3. **toMap()必须指定mergeFunction**：
   ```java
   // 正例
   Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2)
   ```

4. **toMap()注意value为null抛NPE**

5. **subList结果不可强转ArrayList**

6. **keySet()/values()/entrySet()返回对象不可添加元素**

7. **Collections.emptyList()不可修改**

8. **subList场景注意父集合修改导致ConcurrentModificationException**

9. **集合转数组用toArray(T[])**：
   ```java
   String[] array = list.toArray(new String[0]);
   ```

10. **addAll()要做NPE判断**

11. **Arrays.asList()不可修改**

12. **foreach循环里禁止remove/add**，用Iterator

13. **Comparator必须满足三个条件**

### 【推荐】规约

- 泛型定义使用diamond语法：`new HashMap<>(16)`
- 集合初始化指定大小：`(需要存储元素数 / 0.75) + 1`
- 使用entrySet遍历Map，或JDK8的forEach

**Map对null的支持**：

| 集合类 | Key为null | Value为null |
|--------|----------|-------------|
| HashMap | 允许 | 允许 |
| ConcurrentHashMap | 不允许 | 不允许 |
| Hashtable | 不允许 | 不允许 |
| TreeMap | 不允许 | 允许 |

---

## 七、并发处理

### 【强制】规约

1. **单例对象保证线程安全**

2. **线程池指定有意义名称**

3. **线程资源必须通过线程池提供**

4. **禁止Executors创建线程池**，用ThreadPoolExecutor
   - FixedThreadPool/SingleThreadPool：队列长度Integer.MAX_VALUE，可能OOM
   - CachedThreadPool：线程数Integer.MAX_VALUE，可能OOM

5. **SimpleDateFormat线程不安全**：
   ```java
   // 正例：ThreadLocal
   private static final ThreadLocal<DateFormat> df = ThreadLocal.withInitial(
       () -> new SimpleDateFormat("yyyy-MM-dd")
   );
   // JDK8推荐：DateTimeFormatter
   ```

6. **ThreadLocal必须回收**：
   ```java
   try {
       threadLocal.set(value);
       // ...
   } finally {
       threadLocal.remove();
   }
   ```

7. **同步调用考量锁性能**：能无锁不锁，能块锁不全方法锁

8. **多资源加锁保持一致顺序**

9. **锁的正确使用**：
   ```java
   Lock lock = new XxxLock();
   lock.lock();
   try {
       doSomething();
   } finally {
       lock.unlock();
   }
   ```

10. **tryLock必须判断是否持有锁**

11. **并发修改同一记录需加锁**：乐观锁version或悲观锁

12. **多线程定时任务用ScheduledExecutorService**，不用Timer

### 【推荐】规约

- 资金相关用悲观锁：一锁、二判、三更新、四释放
- CountDownLatch异步转同步，确保countDown执行
- Random多线程性能问题用ThreadLocalRandom
- 双重检查锁用volatile
- LongAdder比AtomicLong性能更好

---

## 八、控制语句

### 【强制】规约

1. **switch每个case必须终止**（break/return/continue），必须有default

2. **switch String参数必须先null判断**

3. **if/else/for/while/do必须用大括号**

4. **三目运算符注意自动拆箱NPE**

5. **高并发避免用"等于"判断作中断条件**

### 【推荐】规约

- 超过10行的方法，return/throw后加空行
- 异常分支用卫语句：
  ```java
  public void process(Man man) {
      if (man.isUgly()) return;
      if (man.isPoor()) return;
      // 正常逻辑
  }
  ```
- if-else不超过3层，超过用策略/状态模式
- 复杂逻辑提取为布尔变量
- 不在条件表达式中插入赋值语句
- 循环体优化：对象定义、数据库连接等移到循环外
- 避免取反逻辑

---

## 九、注释规约

### 【强制】规约

1. **类、属性、方法用Javadoc格式**`/** */`

2. **抽象方法必须Javadoc注释**：说明功能、参数、返回值、异常

3. **类必须添加创建者和日期**

4. **方法内单行用//，多行用/* */**

5. **枚举字段必须有注释**

### 【推荐】规约

- 英文不好就用中文注释
- 代码修改同步更新注释
- 删除未使用的字段、方法、参数

### 【参考】规约

- 谨慎注释代码，无用则删除
- 特殊标记注明人和时间：TODO / FIXME

---

## 十、前后端规约

### 【强制】规约

1. **API明确要素**：协议(HTTPS)、域名、路径、请求方法、请求内容、状态码、响应体

2. **空列表返回空数组[]或空集合{}**

3. **服务端错误返回**：HTTP状态码 + errorCode + errorMessage + 用户提示

4. **JSON的key用lowerCamelCase**

5. **超大整数用String返回**，禁止Long（JS精度问题）

6. **URL参数不超过2048字节**

7. **body内容控制长度**

8. **翻页边界处理**：小于1返回第一页，大于总数返回最后一页

9. **内部重定向用forward**

### 【推荐】规约

- 响应设置缓存：`Cache-Control: s-maxage=秒数`
- 使用JSON格式而非XML
- 时间格式统一：`yyyy-MM-dd HH:mm:ss`，GMT时区


path: references/design.md
content: # 设计规约

## 目录
- [一、UML图使用规范](#一uml图使用规范)
- [二、存储与数据结构设计](#二存储与数据结构设计)
- [三、设计原则](#三设计原则)
- [四、敏捷开发误区](#四敏捷开发误区)
- [五、可扩展性设计](#五可扩展性设计)

---

## 一、UML图使用规范

### 【强制】规约

| 场景 | 使用UML图 |
|------|----------|
| User超过1类 + UserCase超过5个 | **用例图** |
| 业务对象状态超过3个 | **状态图** |
| 调用链路涉及对象超过3个 | **时序图** |
| 模型类超过5个且有复杂依赖 | **类图** |
| 超过2个对象协作+复杂流程 | **活动图** |

### 状态图示例（订单状态）

```
┌──────────┐     付款成功      ┌──────────┐     发货       ┌──────────┐
│  已下单   │ ───────────────> │  已付款   │ ────────────> │  已发货   │
└──────────┘                   └──────────┘                └──────────┘
     │                              │                           │
     │ 取消                         │ 取消                      │ 确认收货
     ↓                              ↓                           ↓
┌──────────┐                   ┌──────────┐                ┌──────────┐
│  已取消   │                   │  已取消   │                │  已完成   │
└──────────┘                   └──────────┘                └──────────┘
```

**注意**：已下单与已完成之间不可能直接转换

---

## 二、存储与数据结构设计

### 【强制】规约

**存储方案和底层数据结构设计需评审通过并沉淀为文档**

评审内容包括：
1. 存储介质选型
2. 表结构设计是否满足技术方案
3. 存取性能和存储空间是否满足业务发展
4. 表/字段之间的辩证关系
5. 字段名称、类型、索引

**数据结构变更同样需要评审**

---

## 三、设计原则

### 【推荐】规约

#### 1. 系统架构设计目标

| 目标 | 说明 |
|------|------|
| 确定系统边界 | 技术层面的做与不做 |
| 确定模块关系 | 依赖关系、宏观输入输出 |
| 确定演化原则 | 后续设计的框架和方向 |
| 确定非功能需求 | 安全性、可用性、可扩展性 |

#### 2. 单一职责原则（SRP）

类在设计时只负责一项职责

#### 3. 优先聚合/组合而非继承

```java
// 正例：聚合/组合
public class Car {
    private Engine engine;  // 组合
}

// 继承需符合里氏代换原则
```

#### 4. 依赖倒置原则（DIP）

```java
// 正例：依赖抽象
public class OrderService {
    private PaymentProcessor processor;  // 依赖接口
}

// 反例：依赖具体实现
public class OrderService {
    private AlipayProcessor processor;  // 依赖具体类
}
```

#### 5. 开闭原则（OCP）

对扩展开放，对修改闭合

```java
// 正例：通过扩展支持新功能
public interface PaymentProcessor {
    Result process(Order order);
}

public class AlipayProcessor implements PaymentProcessor { }
public class WechatProcessor implements PaymentProcessor { }
public class NewPaymentProcessor implements PaymentProcessor { }  // 扩展
```

#### 6. DRY原则（Don't Repeat Yourself）

```java
// 正例：抽取公共方法
public class UserService {
    public void createUser(UserDTO dto) {
        validateUser(dto);  // 复用
    }

    public void updateUser(UserDTO dto) {
        validateUser(dto);  // 复用
    }

    private void validateUser(UserDTO dto) {
        // 校验逻辑
    }
}
```

---

## 四、敏捷开发误区

### 【推荐】规约

**误区**：敏捷开发 = 讲故事 + 编码 + 发布

**正解**：
- 敏捷是快速交付迭代可用的系统
- 省略多余设计方案
- 摒弃传统审批流程
- **核心关键点的设计和文档沉淀仍然需要**

**反例**：以敏捷为借口催进度，系统代码像面条，一年后大规模重构

---

## 五、可扩展性设计

### 【参考】规约

**可扩展性本质**：找到系统变化点，隔离变化点

**极致扩展性标志**：需求新增不会在原有代码上做任何修改

```java
// 正例：策略模式隔离变化
public interface PricingStrategy {
    BigDecimal calculate(Order order);
}

public class NormalPricing implements PricingStrategy { }
public class VipPricing implements PricingStrategy { }
public class PromotionPricing implements PricingStrategy { }

// 新增定价策略只需新增类，无需修改现有代码
```

### 设计文档作用

1. **明确需求、理顺逻辑、后期维护**
2. **避免为了设计而设计**
3. **代码即文档是错误的观点**：
   - 清晰代码只是文档片断
   - 深度调用、依赖关系需要文档呈现


path: references/exception-log.md
content: # 异常日志规约

## 目录
- [一、错误码](#一错误码)
- [二、异常处理](#二异常处理)
- [三、日志规约](#三日志规约)

---

## 一、错误码

### 【强制】规约

1. **错误码制定原则**：快速溯源、沟通标准化

2. **错误码不体现版本号和错误等级**

3. **全部正常返回00000**

4. **错误码格式**：5位字符串 = 错误来源(1位) + 数字编号(4位)

   | 来源 | 含义 | 说明 |
   |------|------|------|
   | A | 用户端错误 | 参数错误、版本过低、支付超时等 |
   | B | 当前系统错误 | 业务逻辑出错、程序健壮性差等 |
   | C | 第三方服务错误 | CDN出错、消息投递超时等 |

5. **编号先到先得**，不与业务/组织架构挂钩

6. **避免随意定义新错误码**，优先使用已有错误码

7. **错误码不直接输出给用户**

### 【推荐】规约

- 业务独特信息由errorMessage承载
- 第三方错误码可转义（C→B），带上原错误码

### 【参考】规约

- 一级宏观错误码：`A0001`(用户端错误) / `B0001`(系统执行出错) / `C0001`(调用第三方出错)

### 常用错误码示例

| 错误码 | 描述 |
|--------|------|
| 00000 | 一切OK |
| A0001 | 用户端错误 |
| A0100 | 用户注册错误 |
| A0111 | 用户名已存在 |
| A0121 | 密码长度不够 |
| B0001 | 系统执行出错 |
| C0001 | 调用第三方服务出错 |

---

## 二、异常处理

### 【强制】规约

1. **预检查规避RuntimeException**：
   ```java
   // 正例
   if (obj != null) { obj.method(); }
   // 反例
   try { obj.method(); } catch (NullPointerException e) { }
   ```

2. **异常不用于流程控制**：效率比条件判断低很多

3. **catch区分异常类型**：对非稳定代码分类处理

4. **捕获后必须处理**：
   - 不能空处理
   - 不处理则向上抛出
   - 最外层必须转化为用户可理解内容

5. **事务场景注意手动回滚**

6. **finally块关闭资源**：
   ```java
   // JDK7+ 推荐
   try (InputStream is = new FileInputStream(file)) {
       // ...
   }
   ```

7. **禁止在finally中使用return**：
   ```java
   // 反例：会丢弃try中的return
   private int x = 0;
   public int checkReturn() {
       try { return ++x; }
       finally { return ++x; }  // 返回2而非1
   }
   ```

8. **捕获异常与抛出异常匹配**（或是父类）

9. **RPC/动态类用Throwable拦截**

### 【推荐】规约

1. **返回null时必须注释说明**

2. **防止NPE的场景**：
   - 返回基本类型但return包装对象
   - 数据库查询结果
   - 集合元素即使isNotEmpty也可能为null
   - 远程调用返回对象
   - Session获取的数据
   - 级联调用`obj.getA().getB().getC()`

3. **使用自定义异常**：
   - 推荐：`DAOException` / `ServiceException`
   - 避免：直接抛`RuntimeException` / `Exception` / `Throwable`

### 【参考】规约

**RPC返回方式选择**：
- 公司外HTTP/API接口：使用errorCode
- 应用内部：异常抛出
- 跨应用RPC：Result方式封装

```java
// Result方式示例
public class Result<T> {
    private boolean success;
    private String errorCode;
    private String errorMessage;
    private T data;
}
```

---

## 三、日志规约

### 【强制】规约

1. **使用日志框架SLF4J**，不直接用Log4j/Logback：
   ```java
   import org.slf4j.Logger;
   import org.slf4j.LoggerFactory;
   private static final Logger logger = LoggerFactory.getLogger(Test.class);
   ```

2. **日志保存时间**：
   - 至少15天（异常可能"周"为频次）
   - 敏感操作日志不少于6个月（法规要求）

3. **日志文件命名**：
   - 当天：`应用名.log`
   - 历史：`应用名.log.yyyy-MM-dd`
   - 路径：`/home/admin/应用名/logs/`

4. **扩展日志命名**：`appName_logType_logName.log`

5. **字符串拼接用占位符**：
   ```java
   // 正例
   logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
   ```

6. **trace/debug/info必须级别判断**：
   ```java
   if (logger.isDebugEnabled()) {
       logger.debug("Current ID is: {} and name is: {}", id, getName());
   }
   ```

7. **避免重复打印**：设置`additivity=false`

8. **生产环境禁止**：
   - `System.out` / `System.err`
   - `e.printStackTrace()`

9. **异常日志包含**：案发现场信息 + 异常堆栈
   ```java
   logger.error("inputParams:{} and errorMessage:{}",
       params, e.getMessage(), e);
   ```

10. **禁止直接JSON工具转换对象**

### 【推荐】规约

1. **谨慎记录日志**：
   - 生产禁止debug日志
   - 有选择输出info日志
   - warn注意输出量

2. **参数错误用warn**，不用error

3. **日志语言**：英文优先，说不清用中文


path: references/mysql.md
content: # MySQL数据库规约

## 目录
- [一、建表规约](#一建表规约)
- [二、索引规约](#二索引规约)
- [三、SQL语句](#三sql语句)
- [四、ORM映射](#四orm映射)

---

## 一、建表规约

### 【强制】规约

1. **布尔字段命名**：`is_xxx`，类型`unsigned tinyint`（1表示是，0表示否）

2. **表名/字段名规范**：
   - 必须使用小写字母或数字
   - 禁止数字开头
   - 正例：`aliyun_admin` / `rdc_config` / `level3_name`

3. **表名不使用复数名词**

4. **禁用保留字**：`desc` / `range` / `match` / `delayed`等

5. **索引命名规范**：
   - 主键索引：`pk_字段名`
   - 唯一索引：`uk_字段名`
   - 普通索引：`idx_字段名`

6. **小数类型用decimal**，禁止使用float和double

7. **字符串长度几乎相等时用char定长**

8. **varchar长度不超过5000**，超过则用text独立建表

9. **表必备三字段**：
   ```sql
   CREATE TABLE example (
       `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
       `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
       `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
       PRIMARY KEY (`id`)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='示例表';
   ```

### 【推荐】规约

1. **表命名**：`业务名称_表的作用`
2. **库名与应用名称一致**
3. **分库分表时机**：单表超过500万行或2GB

### 存储长度参考

| 对象 | 范围 | 类型 | 字节 |
|------|------|------|------|
| 年龄 | 150岁内 | tinyint unsigned | 1 |
| 数百岁 | smallint unsigned | 2 |
| 数千万年 | int unsigned | 4 |
| 约50亿年 | bigint unsigned | 8 |

---

## 二、索引规约

### 【强制】规约

1. **唯一特性字段必须建唯一索引**

2. **超过3个表禁止join**

3. **varchar字段索引必须指定长度**：
   ```sql
   CREATE INDEX idx_name ON user(name(20));
   ```

4. **页面搜索严禁左模糊或全模糊**

### 【推荐】规约

1. **利用索引有序性**：
   ```sql
   -- 索引：idx_a_b_c
   SELECT * FROM table WHERE a=? AND b=? ORDER BY c;
   ```

2. **利用覆盖索引**避免回表

3. **延迟关联优化超多分页**：
   ```sql
   SELECT t1.*
   FROM user t1,
        (SELECT id FROM user WHERE condition LIMIT 100000, 20) t2
   WHERE t1.id = t2.id;
   ```

4. **SQL性能目标**：至少range级别，要求ref，最好是consts

5. **组合索引区分度最高的在最左边**

---

## 三、SQL语句

### 【强制】规约

1. **统计行数用count(*)**：
   ```sql
   -- 正例
   SELECT count(*) FROM user;
   ```

2. **sum()注意NPE**：
   ```sql
   SELECT IFNULL(SUM(amount), 0) FROM orders;
   ```

3. **判NULL用ISNULL()**

4. **分页count为0直接返回**

5. **禁止使用外键与级联**

6. **禁止使用存储过程**

7. **数据订正先select确认**

8. **多表查询字段加别名限定**：
   ```sql
   SELECT t1.name FROM user AS t1, order AS t2 WHERE t1.id = t2.user_id;
   ```

### 【推荐】规约

1. **表别名用as**，按t1、t2、t3命名
2. **in操作控制在1000个之内**
3. **字符集用utf8mb4**

---

## 四、ORM映射

### 【强制】规约

1. **禁止SELECT \***：
   ```xml
   <select id="listUsers">
       SELECT id, username, email FROM user
   </select>
   ```

2. **参数使用#{}**：
   ```xml
   <!-- 正例：防SQL注入 -->
   <select id="getById">SELECT * FROM user WHERE id = #{id}</select>
   ```

3. **必须定义resultMap**

4. **更新时必须更新update_time**

### 【推荐】规约

**不写大而全的更新接口**：
```xml
<update id="updateUserSelective">
    UPDATE user
    <set>
        <if test="name != null">name = #{name},</if>
        <if test="email != null">email = #{email},</if>
    </set>
    WHERE id = #{id}
</update>
```


path: references/project-structure.md
content: # 工程结构规约

## 目录
- [一、应用分层](#一应用分层)
- [二、领域模型](#二领域模型)
- [三、二方库依赖](#三二方库依赖)
- [四、服务器配置](#四服务器配置)

---

## 一、应用分层

### 推荐分层架构

```
┌─────────────────────────────────────────────────────┐
│                   开放API层                          │
│    (RPC接口/HTTP接口/网关控制)                        │
├─────────────────────────────────────────────────────┤
│                   终端显示层                          │
│    (velocity/JS/JSP/移动端)                          │
├─────────────────────────────────────────────────────┤
│                    Web层                             │
│    (Controller/参数校验/简单业务)                     │
├─────────────────────────────────────────────────────┤
│                   Service层                          │
│    (具体业务逻辑)                                     │
├─────────────────────────────────────────────────────┤
│                   Manager层                          │
│    (通用业务/第三方封装/DAO组合)                       │
├─────────────────────────────────────────────────────┤
│                    DAO层                             │
│    (数据访问/MySQL/Oracle/HBase)                     │
└─────────────────────────────────────────────────────┘
```

### 各层职责

| 层级 | 职责 | 说明 |
|------|------|------|
| **开放API层** | 对外暴露 | RPC接口、HTTP接口、网关 |
| **终端显示层** | 渲染展示 | velocity、JS、JSP、移动端 |
| **Web层** | 访问控制转发 | Controller、参数校验 |
| **Service层** | 业务逻辑 | 具体业务处理 |
| **Manager层** | 通用处理 | 第三方封装、缓存、DAO组合 |
| **DAO层** | 数据访问 | MySQL、Oracle、HBase |

### 分层异常处理

| 层级 | 异常处理方式 |
|------|-------------|
| **DAO层** | catch(Exception) + throw DAOException，不打印日志 |
| **Service层** | 必须记录日志到磁盘 |
| **Web层** | 跳转友好错误页面 |
| **开放接口层** | 转化为错误码和错误信息 |

---

## 二、领域模型

| 模型 | 全称 | 说明 |
|------|------|------|
| **DO** | Data Object | 与数据库表一一对应 |
| **DTO** | Data Transfer Object | 数据传输对象 |
| **BO** | Business Object | 业务对象 |
| **Query** | - | 数据查询对象（超过2个参数封装） |
| **VO** | View Object | 显示层对象 |

**注意**：Query禁止使用Map类传输

---

## 三、二方库依赖

### 【强制】规约

1. **GAV命名规范**：
   - **GroupID**：`com.{公司/BU}.业务线[.子业务线]`，最多4级
   - **ArtifactID**：`产品线名-模块名`

2. **版本号格式**：`主版本号.次版本号.修订号`
   - 主版本号：产品方向改变、大规模API不兼容
   - 次版本号：相对兼容、增加主要功能
   - 修订号：完全兼容、修复BUG
   - **起始版本号必须为1.0.0**

3. **线上不依赖SNAPSHOT版本**

4. **依赖群定义统一版本变量**：
   ```xml
   <properties>
       <spring.version>5.3.20</spring.version>
   </properties>
   ```

5. **禁止相同GAV不同Version**

### 【推荐】规约

1. **依赖声明与版本仲裁分离**：
   ```xml
   <!-- 父POM：版本仲裁 -->
   <dependencyManagement>
       <dependencies>...</dependencies>
   </dependencyManagement>

   <!-- 子POM：依赖声明 -->
   <dependencies>...</dependencies>
   ```

2. **二方库不要有配置项**

---

## 四、服务器配置

### 【推荐】规约

1. **调小TCP time_wait超时时间**：
   ```bash
   # /etc/sysctl.conf
   net.ipv4.tcp_fin_timeout = 30
   ```

2. **调大最大文件句柄数**

3. **JVM配置OOM时输出dump**：
   ```bash
   -XX:+HeapDumpOnOutOfMemoryError
   ```

4. **Xms和Xmx设置相同大小**：
   ```bash
   -Xms4g -Xmx4g
   ```

---

## 项目目录结构示例

```
project-name/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/company/project/
│   │   │       ├── controller/        # Web层
│   │   │       ├── service/           # Service层
│   │   │       │   └── impl/
│   │   │       ├── manager/           # Manager层
│   │   │       ├── dao/               # DAO层
│   │   │       ├── model/             # 领域模型
│   │   │       │   ├── dto/
│   │   │       │   ├── vo/
│   │   │       │   └── query/
│   │   │       └── config/
│   │   └── resources/
│   │       ├── mapper/
│   │       └── application.yml
│   └── test/
│       └── java/
```


path: references/security.md
content: # 安全规约

## 目录
- [权限控制](#权限控制)
- [数据脱敏](#数据脱敏)
- [SQL注入防护](#sql注入防护)
- [参数校验](#参数校验)
- [XSS防护](#xss防护)
- [CSRF防护](#csrf防护)
- [URL重定向安全](#url重定向安全)
- [防重放机制](#防重放机制)

---

## 权限控制

### 【强制】隶属于用户个人的页面或功能必须进行权限控制校验

```java
// 正例：水平权限校验
public void viewMessage(Long messageId, Long userId) {
    Message message = messageDao.getById(messageId);
    if (!message.getUserId().equals(userId)) {
        throw new PermissionDeniedException("无权访问");
    }
}
```

**风险场景**：查看他人私信、修改他人订单、删除他人数据

---

## 数据脱敏

### 【强制】用户敏感数据禁止直接展示，必须脱敏

```java
public class DataMaskUtil {
    // 手机号脱敏：139****1219
    public static String maskPhone(String phone) {
        return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
    }

    // 身份证脱敏：110***********1234
    public static String maskIdCard(String idCard) {
        return idCard.substring(0, 3) + "***********" + idCard.substring(idCard.length() - 4);
    }

    // 银行卡脱敏：6222 **** **** 1234
    public static String maskBankCard(String cardNo) {
        return cardNo.substring(0, 4) + " **** **** " + cardNo.substring(cardNo.length() - 4);
    }
}
```

---

## SQL注入防护

### 【强制】用户输入的SQL参数严格使用参数绑定

```java
// 正例：使用PreparedStatement
public User findByUsername(String username) {
    String sql = "SELECT * FROM user WHERE username = ?";
    return jdbcTemplate.queryForObject(sql, new Object[]{username}, userRowMapper);
}

// 反例：字符串拼接SQL
public User findByUsername(String username) {
    String sql = "SELECT * FROM user WHERE username = '" + username + "'";
    // 危险！
}
```

**MyBatis正确用法**：
- `#{param}` 安全
- `${param}` 不安全，易注入

---

## 参数校验

### 【强制】用户请求传入的任何参数必须做有效性验证

**忽略参数校验可能导致**：
- `page size`过大导致内存溢出
- 恶意`order by`导致数据库慢查询
- 缓存击穿、SSRF、SQL注入、ReDoS

```java
// 正例：参数校验
public PageResult<User> listUsers(UserQuery query) {
    // 分页参数校验
    if (query.getPageNum() == null || query.getPageNum() < 1) {
        query.setPageNum(1);
    }
    if (query.getPageSize() == null || query.getPageSize() > 100) {
        query.setPageSize(20);
    }
    // 排序字段白名单
    if (!ALLOWED_ORDER_FIELDS.contains(query.getOrderBy())) {
        throw new IllegalArgumentException("非法排序字段");
    }
    return userDao.queryPage(query);
}
```

---

## XSS防护

### 【强制】禁止向HTML页面输出未经安全过滤的用户数据

```java
import org.apache.commons.text.StringEscapeUtils;

public String safeOutput(String userInput) {
    return StringEscapeUtils.escapeHtml4(userInput);
}
```

---

## CSRF防护

### 【强制】表单、AJAX提交必须执行CSRF安全验证

```java
@Controller
public class CsrfController {

    @GetMapping("/form")
    public String showForm(HttpSession session, Model model) {
        String csrfToken = UUID.randomUUID().toString();
        session.setAttribute("csrfToken", csrfToken);
        model.addAttribute("csrfToken", csrfToken);
        return "form";
    }

    @PostMapping("/submit")
    public String submitForm(HttpSession session, @RequestParam String csrfToken) {
        String sessionToken = (String) session.getAttribute("csrfToken");
        if (!csrfToken.equals(sessionToken)) {
            throw new SecurityException("CSRF验证失败");
        }
        return "success";
    }
}
```

---

## URL重定向安全

### 【强制】URL外部重定向必须执行白名单过滤

```java
public class RedirectUtil {
    private static final Set<String> ALLOWED_DOMAINS = Set.of(
        "www.example.com", "m.example.com"
    );

    public static String safeRedirect(String targetUrl) {
        try {
            URL url = new URL(targetUrl);
            if (!ALLOWED_DOMAINS.contains(url.getHost())) {
                return "/";
            }
            return targetUrl;
        } catch (MalformedURLException e) {
            return "/";
        }
    }
}
```

---

## 防重放机制

### 【强制】使用平台资源必须实现防重放机制

适用场景：短信、邮件、电话、下单、支付

```java
@Service
public class SmsService {
    // 发送频率限制：1分钟1次
    private static final int INTERVAL_SECONDS = 60;
    // 日发送次数限制：10次
    private static final int DAILY_LIMIT = 10;

    public void sendVerifyCode(String phone) {
        String intervalKey = "sms:interval:" + phone;

        // 频率限制
        if (redisTemplate.hasKey(intervalKey)) {
            throw new BusinessException("发送过于频繁");
        }

        // 日次数限制
        Long count = redisTemplate.opsForValue().increment("sms:daily:" + phone);
        if (count > DAILY_LIMIT) {
            throw new BusinessException("今日发送次数已达上限");
        }

        // 发送验证码...

        redisTemplate.opsForValue().set(intervalKey, "1",
            Duration.ofSeconds(INTERVAL_SECONDS));
    }
}
```


path: references/unit-test.md
content: # 单元测试规约

## 目录
- [AIR原则](#air原则)
- [BCDE原则](#bcde原则)
- [强制规约](#强制规约)
- [推荐规约](#推荐规约)
- [测试代码示例](#测试代码示例)

---

## AIR原则

好的单元测试必须遵守AIR原则：

| 原则 | 含义 | 说明 |
|------|------|------|
| **A**utomatic | 自动化 | 测试执行完全自动化，非交互式 |
| **I**ndependent | 独立性 | 测试用例间不互相调用，不依赖执行顺序 |
| **R**epeatable | 可重复 | 不受外界环境影响，可重复执行 |

---

## BCDE原则

编写单元测试遵守BCDE原则：

| 原则 | 含义 | 说明 |
|------|------|------|
| **B**order | 边界值测试 | 循环边界、特殊取值、特殊时间点、数据顺序 |
| **C**orrect | 正确性测试 | 正确输入得到预期结果 |
| **D**esign | 设计结合 | 与设计文档结合编写测试 |
| **E**rror | 错误测试 | 非法数据、异常流程、业务允许外的情况 |

---

## 强制规约

### 1. 测试自动化

- 测试必须全自动执行，非交互式
- **禁止**使用`System.out`人肉验证
- **必须**使用assert验证结果

### 2. 测试独立性

- 测试用例间**不能互相调用**
- **不能依赖执行顺序**

### 3. 测试可重复性

- 不受外部环境影响（网络、服务、中间件）
- 通过DI注入本地/Mock实现

### 4. 测试粒度

- 至多是**类级别**，一般是**方法级别**
- 单测不负责跨类/跨系统的交互逻辑

### 5. 核心业务增量代码必须单测通过

### 6. 测试代码目录

- **必须**写在`src/test/java`
- **禁止**写在业务代码目录下

---

## 推荐规约

### 1. 测试覆盖率目标

| 类型 | 语句覆盖率 | 分支覆盖率 |
|------|-----------|-----------|
| 一般代码 | 70% | - |
| 核心模块 | 100% | 100% |

**重点测试**：DAO层、Manager层、可重用度高的Service

### 2. 数据库测试

- **不假设**数据库数据存在
- **不直接操作**数据库插入数据
- 使用**程序插入**或**导入数据**方式准备

### 3. 数据库测试数据隔离

- 设定**自动回滚**机制
- 或用**明确前缀标识**测试数据

### 4. 可测性设计

避免以下情况：
- 构造方法做太多事情
- 过多全局变量和静态方法
- 过多外部依赖
- 过多条件语句

### 5. 测试时机

- **项目提测前**完成单元测试
- **不建议**项目发布后补充

---

## 测试代码示例

```java
/**
 * UserService单元测试
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @MockBean
    private ExternalApiService externalApiService;

    private User testUser;

    @Before
    public void setUp() {
        testUser = new User();
        testUser.setUsername("test_user_" + System.currentTimeMillis());
    }

    @After
    public void tearDown() {
        if (testUser.getId() != null) {
            userService.delete(testUser.getId());
        }
    }

    // 正常流程测试 (Correct)
    @Test
    public void testCreateUser_success() {
        UserDTO dto = new UserDTO();
        dto.setUsername("newuser");

        Long userId = userService.createUser(dto);

        assertNotNull(userId);
    }

    // 边界值测试 (Border)
    @Test(expected = IllegalArgumentException.class)
    public void testCreateUser_usernameTooLong() {
        UserDTO dto = new UserDTO();
        dto.setUsername("a".repeat(256));

        userService.createUser(dto);
    }

    // 异常流程测试 (Error)
    @Test(expected = DuplicateUserException.class)
    public void testCreateUser_duplicateUsername() {
        UserDTO dto = new UserDTO();
        dto.setUsername("duplicate");
        userService.createUser(dto);

        userService.createUser(dto);  // 重复创建
    }
}
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)