日常开发问题
--------------------------------------------------------------------------------------------
SpringBoot 实现手机号校验(@Valid + 自定义注解)
@Valid 没有手机号专属校验注解,需要自定义注解配合正则表达式实现,这是最规范、最常用的方案。一、完整实现步骤
1. 引入依赖(已集成无需额外添加)
hibernate-validator,直接用 @Valid 即可。2. 自定义手机号校验注解
@Phone 注解,定义校验规则:import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 自定义手机号校验注解
*/
@Target({ElementType.FIELD, ElementType.PARAMETER}) // 作用在字段/参数上
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Constraint(validatedBy = PhoneValidator.class) // 绑定校验规则类
public @interface Phone {
// 校验失败提示信息
String message() default "手机号格式不正确";
// 必须写的两个属性(校验框架要求)
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
3. 编写手机号校验规则类
ConstraintValidator 接口,写手机号正则(支持中国大陆 11 位手机号):import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 手机号校验规则实现类
*/
public class PhoneValidator implements ConstraintValidator<Phone, String> {
// 中国大陆手机号正则:1开头,第二位3-9,后面9位数字
private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
// 为空不校验(如果需要必填,额外加 @NotBlank)
if (phone == null || phone.trim().isEmpty()) {
return true;
}
// 正则匹配
return phone.matches(PHONE_REGEX);
}
}
4. 在实体类 / DTO 中使用注解
@Phone,必填加 @NotBlank:import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class UserDTO {
@NotBlank(message = "手机号不能为空") // 必填校验
@Phone // 手机号格式校验
private String phone;
private String username;
}
5. 接口层开启校验(@Valid)
@Valid 开启自动校验:import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/add")
public String addUser(@Valid @RequestBody UserDTO userDTO) {
return "添加成功,手机号:" + userDTO.getPhone();
}
}
6. 统一捕获校验异常(可选,优化返回格式)
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 捕获参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidException(MethodArgumentNotValidException e) {
Map<String, String> errorMap = new HashMap<>();
// 获取校验失败的字段和提示信息
e.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String message = error.getDefaultMessage();
errorMap.put(fieldName, message);
});
return errorMap;
}
}
二、测试效果
- 传空手机号:返回
{"phone":"手机号不能为空"} - 传 123456:返回
{"phone":"手机号格式不正确"} - 传 13812345678:校验通过,返回成功信息
三、拓展:快速方案(不自定义注解)
@Pattern 注解写正则(简单场景推荐):import javax.validation.constraints.Pattern;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
总结
- 核心:
@Valid开启校验 + 自定义注解 /@Pattern实现手机号规则 - 正则:
^1[3-9]\\d{9}$适配中国大陆 11 位手机号 - 必填:搭配
@NotBlank防止空值 - 全局异常处理:让校验失败返回更友好的格式
升级 @Phone 注解:支持手机号 + 国际区号(+86 / 0086 / 86)
@Phone 注解可以同时支持:- 纯手机号:
13812345678 - 带区号:
+8613812345678、008613812345678、8613812345678
一、直接替换校验器(最关键)
PhoneValidator.java 替换成下面这段代码:import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 手机号校验(支持 中国大陆手机号 + 国际区号)
* 支持格式:
* 1. 13812345678
* 2. +8613812345678
* 3. 008613812345678
* 4. 8613812345678
*/
public class PhoneValidator implements ConstraintValidator<Phone, String> {
/**
* 正则表达式:支持 手机号 + 区号(+86 / 0086 / 86)
*/
private static final String PHONE_REGEX = "^(\\+86|0086|86)?1[3-9]\\d{9}$";
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
// 为空不校验(需要必填请搭配 @NotBlank)
if (phone == null || phone.trim().isEmpty()) {
return true;
}
// 正则匹配
return phone.matches(PHONE_REGEX);
}
}
二、注解不用改,直接用
@Phone 注解完全不用动,DTO 里还是这样写:@NotBlank(message = "手机号不能为空")
@Phone
private String phone;
三、支持的格式(全部通过校验)
13800138000+86138001380000086138001380008613800138000
四、不支持的格式(自动拦截)
123456(位数不够)013800138000(开头不是 1)+1213800138000(错误区号)
总结
- 升级后 @Phone 支持:纯手机号 + +86 / 0086 / 86 区号
- 只需要替换
PhoneValidator类,其他代码完全不用改 - 正则:
^(\\+86|0086|86)?1[3-9]\\d{9}$
+86 138 1234 5678 这种带空格的也能校验)吗?最终版:支持【手机号 + 座机号】的 @Phone 校验器
支持格式
手机号(带区号也支持)
13812345678+8613812345678008613812345678
座机号(中国固定电话)
010-12345678021-876543210755-123456701012345678(不带横杠)
直接替换:PhoneValidator.java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 手机号 + 座机号 统一校验
* 支持:
* 1. 手机号:13x 14x 15x 17x 18x 19x 16x 15x
* 2. 带区号手机号:+86/0086/86 开头
* 3. 座机号:010-12345678、02112345678、0755-1234567
*/
public class PhoneValidator implements ConstraintValidator<Phone, String> {
/**
* 终极正则:同时匹配 手机号 + 座机号
*/
private static final String PHONE_TEL_REGEX =
// 1. 手机号(支持+86/0086/86)
"^(\\+86|0086|86)?1[3-9]\\d{9}$" +
"|" +
// 2. 座机号(支持带横杠/不带横杠)
"^0\\d{2,3}-?\\d{7,8}$";
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
if (phone == null || phone.trim().isEmpty()) {
return true; // 为空不校验,必填用 @NotBlank
}
return phone.trim().matches(PHONE_TEL_REGEX);
}
}
你的 @Phone 注解不用改!
@NotBlank(message = "联系方式不能为空")
@Phone(message = "手机号或座机号格式不正确")
private String phone;
可通过校验的号码 ✅
- 手机号:
13812345678 - 手机号带区号:
+8613812345678、008613812345678 - 座机带杠:
010-12345678、021-87654321、0755-1234567 - 座机无杠:
01012345678、07551234567
不通过校验 ❌
123456(位数不够)010-1234(座机位数不对)+1213800138000(错误区号)8612345(无效号码)
总结
- 现在你的 @Phone 支持:手机号 + 座机号 + 国际区号
- 只需要替换校验器,其他代码完全不动
- 最通用、最实用的企业级版本
+86 138 1234 5678 这种带空格也能过)吗?phone,新增接口不校验 @NotBlank,更新接口必须校验 @NotBlank。一、核心思路
- 创建两个校验分组:
InsertGroup.class新增UpdateGroup.class更新
- 给
@NotBlank指定 只在 UpdateGroup 生效 - Controller 接口指定使用哪个分组
二、完整实现代码
1. 创建分组接口(空接口即可)
/**
* 新增分组
*/
public interface InsertGroup {}
/**
* 更新分组
*/
public interface UpdateGroup {}
2. DTO 字段上给注解绑定分组
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Null;
@Data
public class UserDTO {
// 重点:
// UpdateGroup 时:必须非空
// InsertGroup 时:不校验 @NotBlank
@NotBlank(message = "手机号不能为空", groups = UpdateGroup.class)
@Phone(message = "手机号格式不正确") // 格式校验永远生效
private String phone;
}
3. Controller 接口指定分组
新增接口 → 不校验非空
@PostMapping("/add")
public String add(
@Validated(InsertGroup.class) // 新增分组
@RequestBody UserDTO dto
) {
return "新增成功";
}
更新接口 → 强制校验非空
@PostMapping("/update")
public String update(
@Validated(UpdateGroup.class) // 更新分组
@RequestBody UserDTO dto
) {
return "更新成功";
}
三、最终效果(完美满足你的需求)
- 新增接口:phone 可以为空,只校验格式
- 更新接口:phone 必须非空 + 格式正确
四、超级重要说明(必看)
- @Valid 不支持分组,必须用 @Validated
- 分组是空接口,仅做标记
- 如果你希望某个注解所有接口都生效,不要写 groups
- 格式校验
@Phone我没加分组 → 所有接口都会校验格式
五、如果你想反过来:新增必填、更新不必填
@NotBlank(groups = InsertGroup.class)
总结
- 用 分组校验 实现:不同接口不同校验规则
@NotBlank(groups = UpdateGroup.class)→ 只在更新时生效- 接口用
@Validated(分组.class)
--------------------------------------------------------------------------------------------
MySQL 字符串 / 英文 按指定自定义顺序排序(完美方案)
苹果、香蕉、梨 → 必须按 梨、苹果、香蕉 排序
a、b、c → 必须按 c、a、b 排序FIELD() 和 FIND_IN_SET()一、最简单最常用:FIELD () 函数
语法
ORDER BY FIELD(字段名, '值1', '值2', '值3', ...)
示例 1:字符串排序
fruit,字段 name:name
香蕉
苹果
梨
SELECT * FROM fruit
ORDER BY FIELD(name, '梨', '苹果', '香蕉');
示例 2:英文排序
SELECT * FROM user
ORDER BY FIELD(status, 'DRAFT', 'AUDIT', 'PASS', 'REJECT');
二、如果顺序值很多,用 FIND_IN_SET ()
ORDER BY FIND_IN_SET(字段名, '值1,值2,值3,值4');
SELECT * FROM product
ORDER BY FIND_IN_SET(color, '红,蓝,绿,黑,白');
三、不在指定列表里的数据放最后
DESC 即可:ORDER BY FIELD(name, '梨','苹果','香蕉') DESC;
四、在 MyBatis / MyBatis-Plus 里怎么写?
1. XML 写法
ORDER BY FIELD(name, '梨', '苹果', '香蕉')
2. MyBatis-Plus 链式调用
queryWrapper.orderByAsc(
"FIELD(name, '梨', '苹果', '香蕉')"
);
总结(超级好记)
- 自定义顺序排序 → 用
FIELD() ORDER BY FIELD(字段, '你要的顺序1','顺序2','顺序3')- 支持中文、英文、任意字符串
- 不在列表里的数据会自动排在最后
必须写所有的值
必须写所有的值
必须写所有的值
必须写所有的值
SELECT x.* FROM zsy_train_legal.lam_case_report x ORDER BY FIELD( LOWER(BUSS_STATUS), 'trlstage', 'rformstage', 'caseling' ) ASC;
--------------------------------------------------------------------------------------------
一、主流工具对比(大文件场景)
| 工具 | 优势 | 核心参数 | 适用场景 |
|---|---|---|---|
| wget | 系统自带、稳定、断点续传 | -c(续传)、-b(后台) |
通用下载、网络不稳、无需额外安装 |
| aria2c | 多线程、分块、速度快、支持 BT / 磁力 | -x(线程)、-s(分块) |
大文件极速下载、多源 / BT |
| rsync | SSH 传输、增量同步、断点续传 | -avzP(归档 + 压缩 + 进度 + 续传) |
服务器间大文件 / 目录同步 |
| axel | 轻量多线程、简单 | -n(线程数) |
追求极简的 HTTP 多线程下载 |
二、详细用法与命令示例
1. wget(最稳妥,系统默认)
# 基础下载
wget https://example.com/large-file.iso
# 断点续传(大文件必备)
wget -c https://example.com/large-file.iso
# 后台下载(关闭终端继续)
wget -b -c https://example.com/large-file.iso
# 指定保存目录/文件名
wget -P /data/downloads -O my-file.iso https://example.com/large-file.iso
# 重试+超时(网络差时)
wget -c -t 100 -T 120 https://example.com/large-file.iso
-c:断点续传(中断后重跑命令即可)-b:后台运行,日志在wget-log-t N:重试 N 次(0 = 无限);-T N:超时 N 秒
2. aria2c(大文件首选,速度最快)
# 安装(Ubuntu/Debian)
sudo apt install aria2
# CentOS/RHEL
sudo yum install aria2
# 4线程+5分块下载(推荐)
aria2c -x 4 -s 5 https://example.com/large-file.iso
# 断点续传(自动支持,重跑即可)
aria2c -c https://example.com/large-file.iso
# 限速(避免占满带宽,单位:K/M/G)
aria2c -x 4 --max-download-limit=5M https://example.com/large-file.iso
# 下载BT/磁力链接
aria2c 'magnet:?xt=urn:btih:xxx'
aria2c file.torrent
-x N:最大连接数(建议 4–8,防服务器限流)-s N:文件分块数(与-x配合提速)- 自动断点续传,重跑命令即可继续
3. rsync(服务器间大文件传输)
# 从远程服务器下载大文件到本地(断点续传)
rsync -avzP user@remote-ip:/path/to/large-file.iso ./local-dir/
# 限速(单位:KB/s)
rsync -avzP --bwlimit=10000 user@remote-ip:/file.iso ./
# 同步目录(增量+断点)
rsync -avzP user@remote-ip:/data/ ./local-data/
-a:归档(保留权限 / 时间);-v:详情;-z:传输压缩-P= --partial --progress:保留未完成文件 + 显示进度(断点核心)- 网络中断后重跑,只传未完成部分,效率极高
4. axel(轻量多线程)
# 安装
sudo apt install axel
# 8线程下载
axel -n 8 https://example.com/large-file.iso
-n N:线程数(默认 8,建议≤16)
三、大文件下载最佳实践
- 必开断点续传:wget 用
-c...
Linux 大日志文件拆分 + 下载完整方案
一、核心拆分命令(推荐 3 种)
split 命令,无需安装任何工具,开箱即用。1. 按文件大小拆分(最常用)
# 格式:split -b 大小 原文件 输出文件名前缀
split -b 100M app.log app_log_
-b 100M:每个文件 100MB(可改50M/200M/1G)- 输出文件:
app_log_aa、app_log_ab、app_log_ac...
2. 按行数拆分(适合需要按行查看)
# 每 10 万行拆分一个文件
split -l 100000 app.log app_log_
3. 带数字后缀(更美观)
split -b 100M -d app.log app_log_
app_log_00、app_log_01、app_log_02二、拆分后快速下载
方法 1:单个文件下载(scp 命令)
# 格式:scp 用户名@服务器IP:文件路径 本地保存路径
scp root@192.168.1.100:/root/app_log_aa ./
方法 2:批量打包下载(推荐)
# 打包所有拆分后的文件
zip log_split.zip app_log_*
scp root@192.168.1.100:/root/log_split.zip ./
方法 3:可视化下载(适合新手)
- 安装简单 HTTP 服务(1 秒安装):
# Python3 环境(绝大多数服务器自带)
python3 -m http.server 8080
- 本地浏览器打开:
服务器IP:8080,直接点击下载。
三、合并还原(可选)
# Linux 合并
cat app_log_* > app_new.log
# Windows 合并(cmd 执行)
copy /b app_log_aa + app_log_ab app_new.log
四、实用示例(完整流程)
# 1. 进入日志目录
cd /var/log/
# 2. 按 100MB 拆分 huge.log 文件
split -b 100M -d huge.log log_part_
# 3. 打包
zip split_log.zip log_part_*
# 4. 本地下载
scp root@服务器IP:/var/log/split_log.zip ./
总结
- 拆分用
split -b 100M 原文件 前缀,简单高效 - 下载优先用
zip 打包 + scp,一次搞定 - 合并用
cat或 Windowscopy命令即可
Linux 精准提取日志:从第 N 行 到 第 M 行 导出到临时文件
sed 最方便,比 tail/head 组合更简单、一行搞定。一、核心命令(直接用)
# 格式:sed -n '开始行,结束行p' 原日志文件 > 临时文件
sed -n '1000,2000p' app.log > temp.log
示例说明
- 提取 第 500 行 到 第 1500 行:
bash运行
sed -n '500,1500p' app.log > temp_500_1500.log - 提取 最后 10000 行(tail 用法):
bash运行
tail -n 10000 app.log > temp_last.log - 提取 从 2000 行 到 文件末尾:
bash运行
sed -n '2000,$p' app.log > temp_2000_end.log
二、你可能需要的 3 种常用场景
1. 只看最近 N 行(最常用)
# 最后 5000 行 → 临时文件
tail -n 5000 app.log > temp.log
2. 精确截取 某行~某行(排查问题专用)
# 第 12345 行 到 第 67890 行
sed -n '12345,67890p' app.log > temp.log
3. 按时间范围截取(进阶)
# 提取 2026-03-21 10:00 到 11:00 的日志
sed -n '/2026-03-21 10:00/,/2026-03-21 11:00/p' app.log > time_temp.log
三、快速查看行号(方便你确定起止行)
# 查看文件总行数
wc -l app.log
# 查看前 10 行(确认开头)
head -n 10 app.log
# 查看最后 10 行(确认结尾)
tail -n 10 app.log
四、小技巧:拆分超大日志(分段导出)
# 分段 1:1-100000行
sed -n '1,100000p' app.log > log_1.log
# 分段 2:100001-200000行
sed -n '100001,200000p' app.log > log_2.log
总结
- 指定起止行 →
sed -n 'start,endp' 源文件 > 临时文件 - 最后 N 行 →
tail -n N 源文件 > 临时文件 - 命令轻量、不占内存,超大日志也能秒执行
明确回答:绝对不会变化!完全安全!
sed -n 'start,endp' 源文件 > 临时文件 这个命令:1. 核心结论
- 源文件(原日志文件)只会被读取,不会被修改、删除、覆盖
- 不会写入任何内容到源文件
- 不会改变源文件的大小、时间、权限
- 是只读操作,生产环境、超大日志随便用
2. 为什么安全?
> 重定向,把输出单独写到了新的临时文件里:sed -n '1000,2000p' app.log > temp.log
app.log= 只读读取temp.log= 新建 / 写入(只有这个文件会变)
3. 什么情况才会改源文件?
-i 参数 才会直接修改源文件!# 危险!会直接修改原文件!
sed -i 'xxx' app.log
4. 最安全的推荐写法(你现在用的就是)
# 提取 1000~2000 行 → 生成 temp.log
# app.log 纹丝不动
sed -n '1000,2000p' app.log > temp.log
总结
e.printStackTrace() 打印到日志文件的正确方法
e.printStackTrace() 只会打印到控制台,不会进日志文件,生产环境排查 bug 非常麻烦。一、最推荐:直接用日志框架打印(企业标准)
log4j / logback / slf4j,直接传异常对象 e 进去,会自动打印完整堆栈到日志文件。try {
// 你的业务代码
} catch (Exception e) {
// ✅ 这一行:完整堆栈信息 自动写入日志文件
log.error("执行XX业务出错啦:", e);
}
关键要点
- 第二个参数传 e,不要拼字符串
e.getMessage() - 这样会打印:异常类型 + 异常信息 + 完整堆栈轨迹
- 日志文件里就能看到,和
printStackTrace()内容一样
二、如果必须用 printStackTrace () 输出到文件
try {
// 代码
} catch (Exception e) {
try (FileOutputStream fos = new FileOutputStream("/tmp/error.log", true);
PrintStream ps = new PrintStream(fos)) {
// 输出到文件
e.printStackTrace(ps);
} catch (IOException ioEx) {
ioEx.printStackTrace();
}
}
三、极简替代:把堆栈转成字符串存日志
try {
// 代码
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String errorStack = sw.toString();
// 输出到日志
log.error("完整堆栈:\n{}", errorStack);
}
最重要的区别(一定要看)
-
❌ 坏写法(控制台输出,不进文件)java运行
e.printStackTrace(); -
✅ 好写法(进日志文件,企业标准)java运行
log.error("业务异常", e);
总结
e.printStackTrace()本身不会写日志文件- 想写文件,用
log.error("提示信息", e)最标准、最简单 - 日志框架会自动把完整堆栈保存到文件
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------

浙公网安备 33010602011771号