WIP
This commit is contained in:
119
CLAUDE.md
Normal file
119
CLAUDE.md
Normal 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`.
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.starry.admin.common.filter;
|
||||
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
|
||||
import com.starry.admin.modules.custom.service.impl.PlayCustomUserInfoServiceImpl;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 请求关联ID过滤器,为每个HTTP请求生成唯一的跟踪ID
|
||||
* 用于日志关联和请求链路追踪
|
||||
*
|
||||
* @author Claude
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class CorrelationFilter implements Filter {
|
||||
|
||||
@Resource
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Resource
|
||||
private PlayClerkUserInfoServiceImpl clerkUserInfoService;
|
||||
|
||||
@Resource
|
||||
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 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());
|
||||
|
||||
// 尝试获取用户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() {
|
||||
// 清理逻辑
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.starry.common.interceptor;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 请求响应日志拦截器
|
||||
* 记录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
|
||||
));
|
||||
|
||||
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
76
start.sh
Executable 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
|
||||
Reference in New Issue
Block a user