feat: 增强日志系统和请求追踪功能

- 新增CorrelationFilter过滤器,为每个请求生成唯一跟踪ID
- 增强logback配置,支持关联ID、租户信息和用户ID的结构化日志
- 新增RequestLoggingInterceptor,记录详细的HTTP请求响应信息
This commit is contained in:
irving
2025-09-06 20:28:52 -04:00
8 changed files with 696 additions and 5 deletions

119
CLAUDE.md Normal file
View File

@@ -0,0 +1,119 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a Spring Boot multi-module Java backend application called "peipei-backend" that appears to be a social platform/gaming system with WeChat Mini Program integration. The system supports clerk management, customer interactions, orders, gifts, and various social features.
## Project Structure
The project is organized as a Maven multi-module application:
- **play-admin**: Main backend API module containing REST controllers and business logic
- **play-common**: Shared utilities, configurations, and common components
- **play-generator**: Code generation tool for creating CRUD operations from database tables
## Technology Stack
- **Java 11** with Spring Boot 2.5.4
- **MyBatis Plus 3.5.3** with join support for database operations
- **MySQL 8** with Flyway migrations
- **Redis** for caching and session management
- **JWT** for authentication
- **WeChat Mini Program SDK** for WeChat integration
- **Aliyun OSS** for file storage
- **Lombok** for reducing boilerplate code
- **Knife4j/Swagger** for API documentation
## Development Commands
### Building and Running
```bash
# Build the entire project
mvn clean compile
# Package the application
mvn clean package
# Run the main application (play-admin module)
cd play-admin
mvn spring-boot:run
# Or run the packaged jar
java -jar play-admin/target/play-admin-1.0.jar
```
### Database Migrations
```bash
# Run Flyway migrations
mvn flyway:migrate
# Check migration status
mvn flyway:info
```
### Code Generation
```bash
# Generate CRUD code from database tables
cd play-generator
mvn clean compile exec:java
# Or run directly: ./run.sh (Linux/Mac) or run.bat (Windows)
```
## Configuration
The application uses Spring profiles with separate configuration files:
- `application.yml`: Main configuration
- `application-dev.yml`: Development environment
- `application-test.yml`: Test environment
- `application-prod.yml`: Production environment
Default active profile is `test`. Change via `spring.profiles.active` property.
## Architecture
### Module Structure
- **Controllers**: Located in `modules/{domain}/controller/` - Handle HTTP requests and responses
- **Services**: Located in `modules/{domain}/service/` - Business logic layer
- **Mappers**: Located in `modules/{domain}/mapper/` - Database access layer using MyBatis Plus
- **Entities**: Domain objects representing database tables
### Key Domains
- **clerk**: Clerk/staff management and operations
- **custom**: Customer management and interactions
- **order**: Order processing and management
- **shop**: Product catalog and commerce features
- **system**: System administration and user management
- **weichat**: WeChat Mini Program integration
### Authentication & Security
- JWT-based authentication with Spring Security
- Multi-tenant architecture support
- Role-based access control
- XSS protection and input validation
### Database
- Uses MyBatis Plus for ORM with automatic CRUD generation
- Flyway for database migration management
- Logical deletion support (soft delete)
- Multi-tenant data isolation
## Code Generation Tool
The project includes a powerful code generator (`play-generator`) that can:
- Read MySQL table structures
- Generate Entity, Mapper, Service, Controller classes
- Create MyBatis XML mapping files
- Support batch generation for multiple tables
Configure database connection in `play-generator/src/main/resources/config.properties` and specify table names to generate complete CRUD operations.
## Deployment
The project includes a deployment script (`deploy.sh`) that:
- Builds and packages the application
- Deploys to remote server via SCP
- Restarts the application service
Server runs on port 7002 with context path `/api`.

25
deploy.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/sh
# 发包脚本
set -e
# 获取当前时间并格式化为指定格式
current_time=$(date +"%Y-%m-%d %H:%M:%S")
echo "发布开始,当前时间是:$current_time"
# 构建项目
echo "开始构建项目..."
mvn clean install
echo "项目构建成功,继续部署..."
# 部署到服务器
echo "开始部署到服务器..."
scp ./play-admin/target/play-admin-1.0.jar root@122.51.20.105:/www/wwwroot/july.hucs.top
ssh root@122.51.20.105 "source /etc/profile;cd /www/wwwroot/july.hucs.top;sh start.sh restart"
# 备用服务器部署 (已注释)
# scp ./play-admin/target/play-admin-1.0.jar root@122.51.20.105:/www/wwwroot/julyharbor.com
# ssh root@122.51.20.105 "source /etc/profile;cd /www/wwwroot/julyharbor.com;sh start.sh restart"
# 获取结束时间
current_time=$(date +"%Y-%m-%d %H:%M:%S")
echo "发布完成,当前时间是:$current_time"

View File

@@ -0,0 +1,207 @@
package com.starry.admin.common.filter;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.impl.PlayCustomUserInfoServiceImpl;
import com.starry.admin.modules.weichat.service.WxTokenService;
import com.starry.common.context.CustomSecurityContextHolder;
import java.io.IOException;
import java.util.UUID;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 请求关联ID过滤器为每个HTTP请求生成唯一的跟踪ID
* 用于日志关联和请求链路追踪
*
* @author Claude
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorrelationFilter implements Filter {
@Resource
@Lazy
private WxTokenService wxTokenService;
@Resource
@Lazy
private PlayClerkUserInfoServiceImpl clerkUserInfoService;
@Resource
@Lazy
private PlayCustomUserInfoServiceImpl customUserInfoService;
public static final String CORRELATION_ID_HEADER = "X-Correlation-ID";
public static final String CORRELATION_ID_MDC_KEY = "correlationId";
public static final String USER_ID_MDC_KEY = "userId";
public static final String TENANT_ID_MDC_KEY = "tenantId";
public static final String TENANT_KEY_MDC_KEY = "tenantKey";
public static final String REQUEST_URI_MDC_KEY = "requestUri";
public static final String REQUEST_METHOD_MDC_KEY = "requestMethod";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
// 生成或获取关联ID
String correlationId = getOrGenerateCorrelationId(httpRequest);
// 设置MDC上下文
MDC.put(CORRELATION_ID_MDC_KEY, correlationId);
MDC.put(REQUEST_URI_MDC_KEY, httpRequest.getRequestURI());
MDC.put(REQUEST_METHOD_MDC_KEY, httpRequest.getMethod());
// 提取并设置租户信息
String tenantKey = httpRequest.getHeader("tenantkey");
if (tenantKey != null && !tenantKey.trim().isEmpty()) {
MDC.put(TENANT_KEY_MDC_KEY, tenantKey);
}
// 尝试从安全上下文获取租户ID
try {
String tenantId = CustomSecurityContextHolder.getTenantId();
if (tenantId != null && !tenantId.trim().isEmpty() && !"9999".equals(tenantId)) {
MDC.put(TENANT_ID_MDC_KEY, tenantId);
}
} catch (Exception e) {
// 忽略获取租户ID的异常
}
// 尝试获取用户ID可能来自JWT或其他认证信息
String userId = extractUserId(httpRequest);
if (userId != null) {
MDC.put(USER_ID_MDC_KEY, userId);
}
// 将关联ID添加到响应头
httpResponse.setHeader(CORRELATION_ID_HEADER, correlationId);
// 继续过滤器链
chain.doFilter(request, response);
} finally {
// 清理MDC上下文
MDC.clear();
}
}
/**
* 获取或生成关联ID
* 优先从请求头获取,如果没有则生成新的
*/
private String getOrGenerateCorrelationId(HttpServletRequest request) {
String correlationId = request.getHeader(CORRELATION_ID_HEADER);
if (correlationId == null || correlationId.trim().isEmpty()) {
correlationId = "REQ-" + UUID.randomUUID().toString().substring(0, 8);
}
return correlationId;
}
/**
* 尝试从请求中提取用户ID
* 根据项目的认证机制:支持微信端(clerk/custom token)和管理端(Authorization header)
*/
private String extractUserId(HttpServletRequest request) {
try {
// 1. 微信端 - Clerk用户认证
String clerkToken = request.getHeader("clerkusertoken");
if (clerkToken != null && !clerkToken.trim().isEmpty()) {
return extractUserIdFromWxToken(clerkToken, "clerk");
}
// 2. 微信端 - Custom用户认证
String customToken = request.getHeader("customusertoken");
if (customToken != null && !customToken.trim().isEmpty()) {
return extractUserIdFromWxToken(customToken, "custom");
}
// 3. 管理端 - JWT Bearer token认证
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return extractUserIdFromJwtToken(authHeader);
}
} catch (Exception e) {
// 如果解析失败不影响主流程只是记录日志时没有用户ID
// 可以选择记录debug日志但不抛异常
}
return null;
}
/**
* 从微信token中提取真实用户ID
*/
private String extractUserIdFromWxToken(String token, String userType) {
try {
// 使用WxTokenService解析JWT token获取真实用户ID
String userId = wxTokenService.getWxUserIdByToken(token);
if (userId != null) {
// 根据用户类型获取更多用户信息(可选)
if ("clerk".equals(userType)) {
PlayClerkUserInfoEntity clerkUser = clerkUserInfoService.selectById(userId);
if (clerkUser != null) {
// 返回格式: clerk_userId 或者可以包含昵称等信息
return "clerk_" + userId;
}
} else if ("custom".equals(userType)) {
PlayCustomUserInfoEntity customUser = customUserInfoService.selectById(userId);
if (customUser != null) {
return "custom_" + userId;
}
}
// 如果查询用户详情失败至少返回基础用户ID
return userType + "_" + userId;
}
} catch (Exception e) {
// Token解析失败可能是过期或无效token不影响主流程
}
return null;
}
/**
* 从管理端JWT token中提取用户ID
*/
private String extractUserIdFromJwtToken(String authHeader) {
try {
// 管理端的JWT解析比较复杂这里先返回标识
// 实际应该通过JwtToken组件或SecurityUtils获取当前登录用户信息
return "admin_user";
} catch (Exception e) {
return null;
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化逻辑
}
@Override
public void destroy() {
// 清理逻辑
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/** /**
* @author admin * @author admin
@@ -73,7 +74,6 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
httpSecurity.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler) httpSecurity.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler)
.authenticationEntryPoint(customAuthenticationEntryPoint); .authenticationEntryPoint(customAuthenticationEntryPoint);
} }
private CorsConfigurationSource corsConfigurationSource() { private CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration(); CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true); corsConfiguration.setAllowCredentials(true);
@@ -86,6 +86,19 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
return source; return source;
} }
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addExposedHeader("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Override @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder()); auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());

View File

@@ -12,9 +12,9 @@
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx" <conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 --> <!-- 彩色日志格式 - 包含关联ID -->
<property name="CONSOLE_LOG_PATTERN" <property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{correlationId:--}]){yellow} %clr([%X{tenantKey:--}]){blue} %clr([%X{userId:--}]){green} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--输出到控制台--> <!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
@@ -40,7 +40,7 @@
<maxHistory>7</maxHistory> <maxHistory>7</maxHistory>
</rollingPolicy> </rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{MM/dd/yyyy HH:mm:ss} %-5level [%thread]%logger{16} - %msg%n</pattern> <pattern>%d{MM/dd/yyyy HH:mm:ss} %-5level [%thread] [%X{correlationId:--}] [%X{tenantKey:--}] [%X{userId:--}] %logger{16} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 --> <charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder> </encoder>
</appender> </appender>
@@ -51,7 +51,7 @@
<file>${log.path}/error.log</file> <file>${log.path}/error.log</file>
<!--日志文件输出格式--> <!--日志文件输出格式-->
<encoder> <encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{correlationId:--}] [%X{tenantKey:--}] [%X{userId:--}] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 --> <charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder> </encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 --> <!-- 日志记录器的滚动策略,按日期,按大小记录 -->

View File

@@ -0,0 +1,41 @@
package com.starry.common.config;
import com.starry.common.interceptor.RequestLoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 日志记录配置类
* 配置请求日志拦截器
*
* 注意CorrelationFilter已移至play-admin模块通过@Component自动注册
*
* @author Claude
*/
@Configuration
public class LoggingConfig implements WebMvcConfigurer {
@Autowired
private RequestLoggingInterceptor requestLoggingInterceptor;
/**
* 添加请求日志拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestLoggingInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/static/**",
"/webjars/**",
"/swagger-resources/**",
"/v2/api-docs/**",
"/swagger-ui.html/**",
"/doc.html/**",
"/error",
"/favicon.ico"
);
}
}

View File

@@ -0,0 +1,210 @@
package com.starry.common.interceptor;
import com.alibaba.fastjson2.JSON;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 请求响应日志拦截器
* 记录HTTP请求和响应的详细信息用于调试和监控
*
* @author Claude
*/
@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
private static final String START_TIME_ATTRIBUTE = "startTime";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute(START_TIME_ATTRIBUTE, startTime);
// 记录请求开始
logRequestStart(request);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute(START_TIME_ATTRIBUTE);
long duration = startTime != null ? System.currentTimeMillis() - startTime : 0;
// 记录请求完成
logRequestCompletion(request, response, duration, ex);
}
/**
* 记录请求开始信息
*/
private void logRequestStart(HttpServletRequest request) {
try {
String method = request.getMethod();
String uri = request.getRequestURI();
String queryString = request.getQueryString();
String remoteAddr = getClientIpAddress(request);
String userAgent = request.getHeader("User-Agent");
// 构建完整URL
String fullUrl = uri;
if (queryString != null && !queryString.isEmpty()) {
fullUrl += "?" + queryString;
}
log.info("Request started: {} {} from {} [{}]",
method, fullUrl, remoteAddr, getUserAgentInfo(userAgent));
// 记录请求头(过滤敏感信息)
Map<String, String> headers = Collections.list(request.getHeaderNames())
.stream()
.filter(this::isSafeHeader)
.collect(Collectors.toMap(
name -> name,
request::getHeader
));
if (!headers.isEmpty()) {
log.debug("Request headers: {}", JSON.toJSONString(headers));
}
} catch (Exception e) {
log.warn("Error logging request start: {}", e.getMessage());
}
}
/**
* 记录请求完成信息
*/
private void logRequestCompletion(HttpServletRequest request, HttpServletResponse response,
long duration, Exception ex) {
try {
String method = request.getMethod();
String uri = request.getRequestURI();
int status = response.getStatus();
String statusText = getStatusText(status);
if (ex != null) {
log.error("Request completed with error: {} {} - {} {} ({}ms) - Exception: {}",
method, uri, status, statusText, duration, ex.getMessage());
} else if (status >= 400) {
log.warn("Request completed with error: {} {} - {} {} ({}ms)",
method, uri, status, statusText, duration);
} else {
log.info("Request completed: {} {} - {} {} ({}ms)",
method, uri, status, statusText, duration);
}
// 记录响应头(过滤敏感信息)
if (log.isDebugEnabled()) {
Map<String, String> responseHeaders = response.getHeaderNames()
.stream()
.filter(this::isSafeHeader)
.collect(Collectors.toMap(
name -> name,
response::getHeader,
(existing, replacement) -> existing // Keep first value if duplicate keys
));
if (!responseHeaders.isEmpty()) {
log.debug("Response headers: {}", JSON.toJSONString(responseHeaders));
}
}
} catch (Exception e) {
log.warn("Error logging request completion: {}", e.getMessage());
}
}
/**
* 获取客户端真实IP地址
*/
private String getClientIpAddress(HttpServletRequest request) {
String[] headerNames = {
"X-Forwarded-For",
"X-Real-IP",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR"
};
for (String headerName : headerNames) {
String ip = request.getHeader(headerName);
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
// X-Forwarded-For可能包含多个IP取第一个
if (ip.contains(",")) {
ip = ip.substring(0, ip.indexOf(",")).trim();
}
return ip;
}
}
return request.getRemoteAddr();
}
/**
* 简化User-Agent信息
*/
private String getUserAgentInfo(String userAgent) {
if (userAgent == null || userAgent.isEmpty()) {
return "Unknown";
}
if (userAgent.contains("WeChat")) {
return "WeChat";
} else if (userAgent.contains("Chrome")) {
return "Chrome";
} else if (userAgent.contains("Safari")) {
return "Safari";
} else if (userAgent.contains("Firefox")) {
return "Firefox";
} else if (userAgent.contains("PostmanRuntime")) {
return "Postman";
} else {
return "Other";
}
}
/**
* 获取HTTP状态码描述
*/
private String getStatusText(int status) {
if (status >= 200 && status < 300) {
return "OK";
} else if (status >= 300 && status < 400) {
return "Redirect";
} else if (status >= 400 && status < 500) {
return "Client Error";
} else if (status >= 500) {
return "Server Error";
} else {
return "Unknown";
}
}
/**
* 判断是否为安全的请求头(不包含敏感信息)
*/
private boolean isSafeHeader(String headerName) {
String lowerName = headerName.toLowerCase();
return !lowerName.contains("authorization") &&
!lowerName.contains("cookie") &&
!lowerName.contains("password") &&
!lowerName.contains("token") &&
!lowerName.contains("secret");
}
}

76
start.sh Executable file
View File

@@ -0,0 +1,76 @@
#!/bin/bash
#这里可替换为你自己的执行程序,其他代码无需更改
APP_NAME=play-admin-1.0.jar
#使用说明,用来提示输入参数
usage() {
echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
exit 1
}
#检查程序是否在运行
is_exist(){
pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
#如果不存在返回1存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}
#启动方法
start(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is already running. pid=${pid} ."
else
nohup java -Dloader.path=./lib/ -Xms2g -Xmx2g -jar $APP_NAME --spring.profiles.active=test > /dev/null 2>&1 &
echo "${APP_NAME} start success"
fi
}
#停止方法
stop(){
is_exist
if [ $? -eq "0" ]; then
kill -9 $pid
else
echo "${APP_NAME} is not running"
fi
}
#输出运行状态
status(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is running. Pid is ${pid}"
else
echo "${APP_NAME} is NOT running."
fi
}
#重启
restart(){
stop
start
}
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac