通用模板
运行/停止 Java 项目 - Linux
#!/bin/bash
# 定义要清理的 Redis Key 数组
REDIS_KEY_PATTERNS=(
"xxx-token:*"
"xxx-code:*"
"xxx-key"
)
# Redis连接配置(根据需要修改)
REDIS_CLI="redis-cli"
# 如果需要认证,可以这样设置:
# REDIS_CLI="redis-cli -a your_password"
# 如果需要指定主机和端口:
# REDIS_CLI="redis-cli -h your_host -p your_port"
# 检查 Redis 是否可用
if ! $REDIS_CLI ping | grep -q "PONG"; then
echo "Redis 未启动或无法连接!"
exit 1
fi
echo "开始清理 Redis 缓存..."
# 初始化总计数
total_deleted=0
# 逐个模式清理
for pattern in "${REDIS_KEY_PATTERNS[@]}"; do
echo "清理模式: $pattern"
# 初始化变量
cursor=0
pattern_deleted=0
# 使用 SCAN 和 DEL 逐步清理键
while :; do
# SCAN 返回游标和匹配的键列表
result=$($REDIS_CLI scan $cursor match "$pattern" count 1000)
cursor=$(echo "$result" | head -n 1)
keys=$(echo "$result" | tail -n +2 | grep -v '^$')
# 删除匹配的键
if [[ ! -z "$keys" ]]; then
count=$(echo "$keys" | wc -l)
$REDIS_CLI del $keys > /dev/null 2>&1
pattern_deleted=$((pattern_deleted + count))
echo " 已删除 $count 个键"
fi
# 如果游标为 0,表示 SCAN 已完成
if [[ "$cursor" == "0" ]]; then
break
fi
done
total_deleted=$((total_deleted + pattern_deleted))
echo "模式 '$pattern' 完成: 删除 $pattern_deleted 个键"
echo
done
echo "Redis 缓存清理完成!"
echo "总共删除键数量: $total_deleted"#!/bin/bash
# 运行参数
PORT=8080
JAVA_HOME=/usr/local/jdk21/jdk-21.0.2
JAR_FILE="./xxx.jar"
PROFILES=dev
# 检查JAR文件是否存在
if [ ! -f "$JAR_FILE" ]; then
echo "错误: JAR文件 $JAR_FILE 不存在!"
exit 1
fi
# 查找占用指定端口的Java进程ID
PID=$(lsof -ti :$PORT)
if [ -z "$PID" ]; then
echo "没有找到占用端口 $PORT 的Java进程。"
else
echo "停止 Java 端口号: $PORT , 进程 (PID: $PID) 中。。。"
# 使用kill命令优雅地停止Java进程
kill -15 $PID 2>/dev/null
# 添加超时机制,避免无限等待
COUNTER=0
while ps -p $PID > /dev/null 2>&1 && [ $COUNTER -lt 30 ]; do
echo "等待 Java 进程 (PID: $PID) 停止... ($((COUNTER+1))/30秒)"
sleep 1
COUNTER=$((COUNTER+1))
done
# 如果优雅停止失败,强制停止
if ps -p $PID > /dev/null 2>&1; then
echo "优雅停止失败,强制停止进程..."
kill -9 $PID 2>/dev/null
sleep 2
fi
echo "Java程序(端口: $PORT) 已完全停止。"
fi
echo "启动Java应用..."
# nohup 是后台启动,不会占用当前窗口
nohup $JAVA_HOME/bin/java \
-Dspring.profiles.active=$PROFILES \
-Xms2048m \
-Xmx2048m \
-Xmn1024m \
-jar "$JAR_FILE" > /dev/null 2>&1 &# 程序运行端口号
PORT=8080
# 查找占用指定端口的Java进程ID
PID=$(lsof -ti :$PORT)
if [ -z "$PID" ]; then
echo "没有找到占用端口 $PORT 的Java进程。"
else
# 使用kill命令停止Java进程
kill -9 $PID
echo "停止 Java 端口号: $PORT , 进程 (PID: $PID) 中。。。"
# 等待Java程序完全停止(每秒检查一次,防止端口占用)
while ps -p $PID > /dev/null; do
echo "等待 Java 进程 (PID: $PID) 停止..."
sleep 1
done
echo "Java程序(占用端口号port: $PORT ) 已完全停止。"
fi#!/bin/bash
# 定义要停止的端口数组
PORTS=(8080 7001 7002)
# 遍历端口数组并停止对应Java进程
for PORT in "${PORTS[@]}"; do
echo "处理端口: $PORT"
# 查找占用指定端口的Java进程ID
PID=$(lsof -ti :$PORT)
if [ -z "$PID" ]; then
echo "没有找到占用端口 $PORT 的Java进程。"
else
echo "停止 Java 端口号: $PORT , 进程 (PID: $PID)"
# 使用kill命令停止Java进程
kill -9 $PID
echo "Java程序(端口: $PORT) 已停止。"
fi
echo
done
echo "所有指定端口的Java程序停止完成。"运行/停止 Java 项目 - Win
Windowns 中关闭 Java 程序手动将 CMD 黑窗口关掉就行(管他多少个,直接右击【全部关闭】)。
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
:: 运行参数
set PORT=8080
set "JAVA_HOME=D:\start-package\back\jdk-8"
set "JAR_FILE=D:\start-package\back\apps\xxx.jar"
set CMD_TITLE=管理端
set PROFILES=dev
:: 设置控制台标题显示应用信息
title Java应用 - %CMD_TITLE% (端口:%PORT%)
:: 参数日志
echo JAVA_HOME: %JAVA_HOME%
echo JAR_FILE: %JAR_FILE%
echo PROFILES: %PROFILES%
:: 检查JAR文件是否存在
if not exist "%JAR_FILE%" (
echo 错误: JAR文件 %JAR_FILE% 不存在!
pause
exit /b 1
)
echo ✓ JAR文件存在
:: 检查Java是否可用
if not exist "%JAVA_HOME%\bin\java.exe" (
echo 错误: Java路径不正确或未安装!
echo 请检查JAVA_HOME: %JAVA_HOME%
pause
exit /b 1
)
echo ✓ Java环境正常
:: 查找占用指定端口的进程
echo 检查端口 %PORT% 占用情况...
set PID=
for /f "tokens=5" %%i in ('netstat -ano ^| findstr ":%PORT% " ^| findstr "LISTENING" 2^>nul') do (
set PID=%%i
)
if defined PID (
echo 发现占用端口 %PORT% 的进程 (PID: %PID%)
echo 正在停止该进程...
taskkill /pid %PID% /f >nul 2>&1
if errorlevel 1 (
echo 警告: 无法停止进程 %PID%,可能进程已退出
) else (
:: 等待进程停止
echo 等待进程停止...
timeout /t 3 /nobreak >nul
echo ✓ 进程已停止
)
) else (
echo ✓ 端口 %PORT% 未被占用
)
echo Java程序(端口: %PORT%) 已完全停止。
:START_APP
echo 启动Java应用...
:: 直接在当前窗口运行Java程序(会显示日志)
"%JAVA_HOME%\bin\java.exe" ^
-Dspring.profiles.active=%PROFILES% ^
-Xms2048m ^
-Xmx2048m ^
-Xmn1024m ^
-jar "%JAR_FILE%":: 此脚本用于关闭指定端口的 【Java进程】 和 【CMD窗口进程】
:: 仅适合关闭单个端口,多个端口貌似兼容性不行,试了很多方法都不能关掉 CMD 窗口
@echo off
setlocal enabledelayedexpansion
:: 设置要停止的端口
set PORT=8080
:: 查找占用指定端口的进程
echo 检查端口 %PORT% 占用情况...
set PID=
for /f "tokens=5" %%i in ('netstat -ano ^| findstr ":%PORT% " ^| findstr "LISTENING" 2^>nul') do (
set PID=%%i
)
if defined PID (
echo 发现占用端口 %PORT% 的进程 (PID: %PID%)
echo 正在停止该进程...
:: 首先获取进程名称,用于查找相关的CMD窗口
set PROCESS_NAME=
for /f "tokens=1" %%n in ('wmic process where processid^="%PID%" get name 2^>nul ^| findstr /v "Name"') do (
set PROCESS_NAME=%%n
)
taskkill /pid %PID% /f >nul 2>&1
if errorlevel 1 (
echo 警告: 无法停止进程 %PID%,可能进程已退出
) else (
:: 等待进程停止
echo 等待进程停止...
timeout /t 2 /nobreak >nul
echo ✓ Java进程已停止
)
:: 查找并关闭运行Java程序的CMD窗口
echo 查找相关的CMD窗口...
set WINDOW_FOUND=0
for /f "tokens=2" %%w in ('tasklist /fi "imagename eq cmd.exe" /fo table /nh') do (
:: 检查这个CMD窗口是否在运行Java程序
for /f "tokens=1" %%j in ('wmic process where "name='cmd.exe' and processid='%%w'" get commandline 2^>nul ^| findstr /i "java.*jar"') do (
echo 关闭相关的CMD窗口 (PID: %%w)...
taskkill /pid %%w /f >nul 2>&1
set WINDOW_FOUND=1
)
)
if !WINDOW_FOUND!==1 (
echo ✓ CMD窗口已关闭
) else (
echo 未找到相关的CMD窗口,可能已自动关闭
)
) else (
echo ✓ 端口 %PORT% 未被占用
)
echo.
echo Java程序(端口: %PORT%) 已完全停止。
echo.
echo 所有指定端口的Java程序停止完成。
:: pause提示
有些极端场景,甲方服务器(Win 系统)无法联网,需要离线部署程序,跑过去帮甲方?呸!不可能!将前后端代码全部打包,让甲方自己传上去,无脑点击一下脚本文件就能自动运行最方便了。
在 Windows 中使用 bat 脚本快速启动一个 Java 程序的前后端,这里提供一个通用模板,下载下来,自己跟着说明玩一下,就能轻松实现一键在 Win 系统上运行 Java 程序。
数据库通用建表语句
ALTER TABLE table_name
ADD COLUMN `version` int NULL DEFAULT NULL COMMENT '乐观锁',
ADD COLUMN `is_delete` int NULL DEFAULT NULL COMMENT '是否删除;0:否,1:是' AFTER `version`,
ADD COLUMN `create_by` varchar(255) NULL COMMENT '创建人' AFTER `create_time`,
ADD COLUMN `update_by` varchar(255) NULL COMMENT '修改人' AFTER `update_time`,
ADD COLUMN `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间' AFTER `is_delete`,
ADD COLUMN `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间' AFTER `create_time`;
-- MySQL 中,date 只包含年月日;datetime 的精度可选范围是 0~6,秒的小数位数,几乎都使用这个时间类型;
-- timestamp 与 datetime 类似,但内部会进行时区转换,用的较少ALTER TABLE "table_name"
ADD (
"version" INT DEFAULT NULL,
"is_delete" INT DEFAULT NULL,
"create_by" VARCHAR2(255) DEFAULT NULL,
"update_by" VARCHAR2(255) DEFAULT NULL,
"create_time" DATE DEFAULT NULL,
"update_time" DATE DEFAULT NULL
-- Oracle 中,使用 DATE 存储时间是最常见的,它能存储的最小单位是秒;
-- 如有特殊业务对时间精度要求较高,可使用 TIMESTAMP 类型
-- "create_time" TIMESTAMP(0) DEFAULT NULL, -- 只存到秒,不包含小数秒部分
-- "create_time" TIMESTAMP, -- 默认小数秒精度为 6 位(微秒,最高精度)
-- "create_time" TIMESTAMP(3), -- 精确到毫秒
);
COMMENT ON COLUMN "table_name"."version" IS '乐观锁';
COMMENT ON COLUMN "table_name"."is_delete" IS '是否删除;0:否,1:是';
COMMENT ON COLUMN "table_name"."create_by" IS '创建人';
COMMENT ON COLUMN "table_name"."update_by" IS '修改人';
COMMENT ON COLUMN "table_name"."create_time" IS '创建时间';
COMMENT ON COLUMN "table_name"."update_time" IS '更新时间';-- 达梦数据库通用建表语言
ALTER TABLE table_name ADD COLUMN "enable_flag" INT;
COMMENT ON COLUMN table_name."enable_flag" IS '0--已禁用 1--已启用';
ALTER TABLE table_name ADD COLUMN "is_delete" INT DEFAULT 0;
COMMENT ON COLUMN table_name."is_delete" IS '0--未删除 1--已删除';
ALTER TABLE table_name ADD COLUMN "create_by" VARCHAR(128);
COMMENT ON COLUMN table_name."create_by" IS '创建人';
ALTER TABLE table_name ADD COLUMN "update_by" VARCHAR(128);
COMMENT ON COLUMN table_name."update_by" IS '修改人';
ALTER TABLE table_name ADD COLUMN "create_time" DATE(0);
COMMENT ON COLUMN table_name."create_time" IS '创建时间';
ALTER TABLE table_name ADD COLUMN "update_time" DATE(0);
COMMENT ON COLUMN table_name."update_time" IS '更新时间';
-- 达梦数据库中,DATE 能精确到秒,最常用;DATETIME 精确到毫秒;
-- TIMESTAMP 最高支持微秒,即 TIMESTAMP(6)数据库通用 Mapper 语法
通用实体类
import com.baomidou.mybatisplus.annotation.*; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.time.LocalDateTime; @Data public class BaseDomain { /** * 主键(使用自定义的 id 生成策略,详见 MP 官网) */ @TableId(value = "id", type = IdType.ASSIGN_ID) private String id; /** * 是否删除 0否 1是 */ @TableLogic private Integer isDelete; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime createTime; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime updateTime; }分页查询
Service// Mybatis-Plus 分页查询示例 @Override public IPage<UserVo> getList(UserDto dto) { Page<User> page = new Page<>(dto.getPageNum, dto.getPageSize); return userMapper.selectUserByPage(page, dto); }Mapperpublic interface UserMapper extends BaseMapper<User> { IPage<UserVo> selectUserByPage(IPage<?> page, @Param("dto") UserDto dto); }mapper.xml<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zjx.UserMapper"> <select id="selectUserByPage" resultType="com.zjx.vo.UserVo"> select u.id, u.name, addr.location, u.update_time from user u left join address addr on u.id = addr.user_id where u.is_delete = 0 <if test="dto.name != null and dto.name != ''"> and u.name like concat('%', #{dto.name}, '%') </if> <if test="dto.userTypes != null and dto.userTypes.size() > 0"> and u.type_id in <foreach collection="dto.userTypes" item="type" open="(" separator="," close=")"> #{type} </foreach> </if> <if test="dto.startTime != null and dto.endTime != null"> and u.update_time between #{dto.startTime} and #{dto.endTime} <!-- and u.update_time >= #{dto.startTime} and u.update_time <= #{dto.endTime} --> </if> -- 排序开始 ORDER BY <choose> <when test="dto.sortType == 0">u.age</when> <when test="dto.sortType == 1">u.age DESC</when> <otherwise>u.update_time DESC</otherwise> </choose> -- 排序结束 </select> </mapper>批量插入
Service// Mybatis-Plus 批量插入示例 @Override public void saveUsers(UserDto dto) { List<User> users = dto.getUsers().stream() .map(item -> item.setId(snowflakeGenerator.next().toString())) .collect(Collectors.toList()); return userMapper.batchInsert(users); }Mapperpublic interface UserMapper extends BaseMapper<User> { void batchInsert(@Param("users") List<User> users); }mapper.xml<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zjx.UserMapper"> <insert id="batchInsert" parameterType="java.util.List"> insert into user(id, name, age) values <foreach collection="users" item="item" separator=","> ( #{item.id}, #{item.name}, #{item.age} ) </foreach> </select> </mapper>
Mybatis 联表查询
Mybatis 的 resultMap 在嵌套对象赋值(复杂对象映射)的场景下是其最强大、最适用的地方,嵌套对象数据格式天然契合 RESTful API 的资源关系表达,数据层次清晰,职责明确,十分推荐使用!
一对一嵌套查询
假设 user 表和 user_info 表是一对一关系,现在需要联表查询将所有用户列表返回给前端。对应的 Java 实体类、视图对象、mapper 接口定义如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {
@TableId
private String id;
private String name;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
public static User create(String name) {
User user = new User();
user.setName(name);
return user;
}
}@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user_info")
public class UserInfo {
@TableId
private String id;
private String userId;
private String school;
private String phone;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
public static UserInfo create(String userId, String school, String phone) {
UserInfo info = new UserInfo();
info.setUserId(userId);
info.setSchool(school);
info.setPhone(phone);
return info;
}
}@Data
public class UserVO {
private String id;
private String name;
/**
* 嵌套用户信息
*/
private UserInfoVO info;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
}@Data
public class UserInfoVO {
private String id;
private String userId;
private String school;
private String phone;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
}@Mapper
public interface UserMapper extends BaseMapper<User> {
List<UserVO> selectUserListOneToOne();
}关键在于 mapper.xml 的定义,resultMap 一对一嵌套使用 association 实现,如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhiruan.spring.ai.demo.back.mapper.UserMapper">
<!-- autoMapping 属性用于自动映射,若不开启,需要手动为每个属性绑定映射关系;开启后仅绑定主键即可 -->
<resultMap type="com.zhiruan.spring.ai.demo.back.entity.vo.UserVO" autoMapping="true" id="UserResult">
<!-- 主键必须显式配置 -->
<id property="id" column="id"/>
<!-- 嵌套的 info 对象 -->
<association property="info" autoMapping="true" javaType="com.zhiruan.spring.ai.demo.back.entity.vo.UserInfoVO">
<!-- 属性名与列名不一致的字段需要显式配置 -->
<id property="id" column="info_id"/>
<result property="createTime" column="info_create_time"/>
<result property="updateTime" column="info_update_time"/>
</association>
</resultMap>
<sql id="selectUserVo">
SELECT
u.id,
u.`name`,
u.create_time,
u.update_time,
info.id AS info_id,
info.user_id,
info.school,
info.phone,
info.create_time as info_create_time,
info.update_time as info_update_time
FROM user u
LEFT JOIN user_info info ON u.id = info.user_id
</sql>
<select id="selectUserListOneToOne" resultMap="UserResult">
<include refid="selectUserVo"/>
WHERE u.create_time IS NOT NULL
</select>
</mapper>这样,最终得到的 UserVO 中,即包含了 user 表的所有字段,也包含了 user_info 表的所有字段,且字段名与表名一致,方便前端使用。
提示
聪明的你可能会有疑问,为什么搞这么复杂一定要嵌套呢?直接在 UserVO 里将查询语句中的所有字段定义一下不就玩事儿,还不用去写 resultMap,这样岂不是更简单?
但是仔细思考一下,什么是单一职责原则,如果联表是三张或者更多,所有字段定义在一个 VO 中,且不说十分雍总,前端也会疑惑,修改用户该传哪个 id 呢,修改 info 该传哪个 id 呢...如果数据层次分明,什么时候该用哪些数据就十分清晰了。

