系统日志/业务日志
This commit is contained in:
parent
d0b905d40a
commit
0b1dc5e473
|
|
@ -10,5 +10,5 @@ public class Constant {
|
||||||
|
|
||||||
public final static Integer PARENT_ID = 0;
|
public final static Integer PARENT_ID = 0;
|
||||||
|
|
||||||
public final static Integer MENU_TYPE = 3;
|
public final static Integer MENU_TYPE = 2;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,18 @@ import com.securitycontrol.common.security.interceptor.HeaderInterceptor;
|
||||||
public class WebMvcConfig implements WebMvcConfigurer
|
public class WebMvcConfig implements WebMvcConfigurer
|
||||||
{
|
{
|
||||||
/** 不需要拦截地址 */
|
/** 不需要拦截地址 */
|
||||||
public static final String[] EXCLUDE_URLS = { "/login", "/logout", "/refresh","/getUserTicket","/sys/logs/saveLogs","/error","/api/ballrisk/findBallGb","/api/ballrisk/getDeviceState","/api/ballrisk/findDeviceStatus" };
|
public static final String[] EXCLUDE_URLS = { "/login/**","/userLogin/**","/sys/sysLog/saveLogs", "/logout", "/refresh","/getUserTicket","/sys/logs/saveLogs","/error","/sys/select/**" };
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry)
|
public void addInterceptors(InterceptorRegistry registry)
|
||||||
{
|
{
|
||||||
/* registry.addInterceptor(getParamSecureInterceptor())
|
|
||||||
.addPathPatterns("/**")
|
|
||||||
.excludePathPatterns(excludeUrls)
|
|
||||||
.order(-10);*/
|
|
||||||
registry.addInterceptor(getHeaderInterceptor())
|
registry.addInterceptor(getHeaderInterceptor())
|
||||||
.addPathPatterns("/**")
|
.addPathPatterns("/**")
|
||||||
.excludePathPatterns(EXCLUDE_URLS)
|
.excludePathPatterns(EXCLUDE_URLS)
|
||||||
.order(-10);
|
.order(-10);
|
||||||
|
registry.addInterceptor(getParamSecureInterceptor())
|
||||||
|
.addPathPatterns("/**")
|
||||||
|
.excludePathPatterns(EXCLUDE_URLS)
|
||||||
|
.order(-10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,12 @@ package com.securitycontrol.common.security.enums;
|
||||||
|
|
||||||
public enum UrlEnums {
|
public enum UrlEnums {
|
||||||
//注释
|
//注释
|
||||||
DICT_URL("/sys/dict/", "字典管理"),
|
DICT_URL("/sys/dict/", "系统管理-字典管理"),
|
||||||
LOG_URL("/sys/logs/", "日志管理"),
|
LOG_URL("/sys/logs/", "系统管理-日志管理"),
|
||||||
menu_url("/sys/menu/", "菜单管理"),
|
menu_url("/sys/menu/", "系统管理-菜单管理"),
|
||||||
ROLE_URL("/sys/role/", "角色管理"),
|
ROLE_URL("/sys/role/", "系统管理-角色管理"),
|
||||||
USER_URL("/userManage/", "用户管理"),
|
USER_URL("/sys/role/", "系统管理-用户管理"),
|
||||||
DEVICE_URL("/device/", "设备管理"),
|
ORG_URL("/sys/role/", "系统管理-组织机构"),
|
||||||
LEDGER_URL("/ledger", "设备流转台帐"),
|
|
||||||
DEVICE_TYPE_URL("/dev/type/", "设备类型管理"),
|
|
||||||
HOME_URL("/home", "综合展示"),
|
|
||||||
NEW_PRO_URL("/newPro", "工程明细"),
|
|
||||||
TEAM_URL("/team", "班组明细"),
|
|
||||||
RISK_URL("/TRiskPressDropRate", "压降率计算"),
|
|
||||||
UAV_URL("/uav", "无人机巡视"),
|
|
||||||
DAILY_URL("/dailyDutyReport/", "值班日报"),
|
|
||||||
DAILY_STATISTIC_URL("/dutyStatistics/", "值班统计"),
|
|
||||||
SUPER_STATISTICS_URL("/superStatistics/", "违章统计"),
|
|
||||||
TODAY_TASK_URL("/todayTask/", "今日任务"),
|
|
||||||
VOI_PHOTO_LIBRARY_URL("/voiPhotoLibrary/", "违章库照片"),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
private final String url;
|
private final String url;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.securitycontrol.common.security.interceptor;
|
||||||
|
|
||||||
|
import com.securitycontrol.common.security.utils.XssRequestWrapper;
|
||||||
|
|
||||||
|
import javax.servlet.*;
|
||||||
|
import javax.servlet.annotation.WebFilter;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
/**
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2024-03-01-15:07
|
||||||
|
* @version:1.0
|
||||||
|
* @description:过滤器,处理request
|
||||||
|
*/
|
||||||
|
@WebFilter
|
||||||
|
public class MyFilter implements Filter{
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
ServletRequest requestWrapper = null;
|
||||||
|
if(servletRequest instanceof HttpServletRequest) {
|
||||||
|
requestWrapper = new XssRequestWrapper((HttpServletRequest) servletRequest);
|
||||||
|
}
|
||||||
|
if(requestWrapper == null) {
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
} else {
|
||||||
|
filterChain.doFilter(requestWrapper, servletResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,11 +12,16 @@ import com.securitycontrol.common.core.utils.ip.IpUtils;
|
||||||
import com.securitycontrol.common.core.web.domain.AjaxResult;
|
import com.securitycontrol.common.core.web.domain.AjaxResult;
|
||||||
import com.securitycontrol.common.security.enums.UrlEnums;
|
import com.securitycontrol.common.security.enums.UrlEnums;
|
||||||
import com.securitycontrol.common.security.utils.SafeUtil;
|
import com.securitycontrol.common.security.utils.SafeUtil;
|
||||||
|
import com.securitycontrol.common.security.utils.SecurityUtils;
|
||||||
import com.securitycontrol.common.security.utils.Sm3Utils;
|
import com.securitycontrol.common.security.utils.Sm3Utils;
|
||||||
import com.securitycontrol.common.security.utils.XssRequestWrapper;
|
import com.securitycontrol.common.security.utils.XssRequestWrapper;
|
||||||
import com.securitycontrol.system.api.RemoteLogService;
|
import com.securitycontrol.system.api.RemoteLogService;
|
||||||
|
import com.securitycontrol.system.api.RemoteUserService;
|
||||||
|
import com.securitycontrol.system.api.domain.SysLog;
|
||||||
import com.securitycontrol.system.api.domain.SysOperLog;
|
import com.securitycontrol.system.api.domain.SysOperLog;
|
||||||
|
import com.securitycontrol.system.api.model.LoginUser;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
import org.springframework.util.PathMatcher;
|
import org.springframework.util.PathMatcher;
|
||||||
|
|
@ -43,51 +48,33 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
private String rnd = null;
|
private String rnd = null;
|
||||||
|
|
||||||
|
|
||||||
private final String whiteURL ="http://10.145.34.32:21001/";
|
private final String whiteURL = "http://127.0.0.1:18080/";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RemoteLogService remoteLogService;
|
private RemoteLogService remoteLogService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RemoteUserService remoteUserService;
|
||||||
|
|
||||||
private static Map<String, List<Double>> requestLogMap = null;
|
private static Map<String, List<Double>> requestLogMap = null;
|
||||||
|
|
||||||
// IResourceService resourceService = (IResourceService) AdapterFactory.getInstance(Constants.CLASS_RESOURCE);
|
// IResourceService resourceService = (IResourceService) AdapterFactory.getInstance(Constants.CLASS_RESOURCE);
|
||||||
|
|
||||||
String urls="/pot/superStatistics/importExcel";
|
|
||||||
String urls1="/pot/todayTask/uploadNoticeVio";
|
|
||||||
String urls2="/userManage/info/";
|
|
||||||
String url3="/files/";
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
System.out.println("进入了拦截器");
|
System.out.println("进入了拦截器");
|
||||||
System.err.println(request.getRequestURI());
|
System.err.println(request.getRequestURI());
|
||||||
if(Objects.equals(urls,request.getRequestURI()) ||
|
|
||||||
Objects.equals(urls1,request.getRequestURI()) ||
|
|
||||||
request.getRequestURI().contains(urls2)
|
|
||||||
){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(StringUtils.isNotBlank(request.getRequestURI())){
|
|
||||||
if(request.getRequestURI().contains(url3)){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
XssRequestWrapper requestWrapper = new XssRequestWrapper(request);
|
XssRequestWrapper requestWrapper = new XssRequestWrapper(request);
|
||||||
String requestUrl = requestWrapper.getRequestURI();
|
String requestUrl = requestWrapper.getRequestURI();
|
||||||
/*if (StringUtil.isEmpty(requestUrl.trim())) {
|
|
||||||
return false;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 防止refer篡改
|
* 防止refer篡改
|
||||||
*/
|
*/
|
||||||
String referUrl= request.getHeader("Referer");
|
/*String referUrl= request.getHeader("Referer");
|
||||||
if(!whiteURL.equals(referUrl)){
|
if(!Objects.equals(whiteURL,referUrl)){
|
||||||
returnJson(response,"请求来源不正确!",500);
|
returnJson(response,"请求来源不正确!",500);
|
||||||
return false;
|
return false;
|
||||||
}
|
}*/
|
||||||
/**
|
/**
|
||||||
* 白名单中不验证参数
|
* 白名单中不验证参数
|
||||||
*/
|
*/
|
||||||
|
|
@ -98,7 +85,7 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
if (!requestWrapper.isChecked()) {
|
if (!requestWrapper.isChecked()) {
|
||||||
log.error("输入值非法{}", requestWrapper.getQueryString());
|
log.error("输入值非法{}", requestWrapper.getQueryString());
|
||||||
|
|
||||||
returnJson(response,"输入值非法!",500);
|
returnJson(response, "输入值非法!", 500);
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +96,7 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
Map<String, String[]> map = requestWrapper.getParameterMap();
|
Map<String, String[]> map = requestWrapper.getParameterMap();
|
||||||
boolean checkParameterMap = checkParameterMap(map, requestUrl);
|
boolean checkParameterMap = checkParameterMap(map, requestUrl);
|
||||||
if (!checkParameterMap) {
|
if (!checkParameterMap) {
|
||||||
returnJson(response,"输入值非法",500);
|
returnJson(response, "输入值非法", 500);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -123,65 +110,103 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
*/
|
*/
|
||||||
String readerParam = requestWrapper.getReaderParam();
|
String readerParam = requestWrapper.getReaderParam();
|
||||||
// 判断是否是文件上传,是不对流参数进行验证
|
// 判断是否是文件上传,是不对流参数进行验证
|
||||||
String uplFile="uploadFile",upImage="uploadImage";
|
String uplFile = "uploadFile", upImage = "uploadImage";
|
||||||
if (!requestUrl.contains(uplFile) && !requestUrl.contains(upImage)) {
|
if (!requestUrl.contains(uplFile) && !requestUrl.contains(upImage)) {
|
||||||
boolean checkReader = checkReader(readerParam, requestUrl);
|
boolean checkReader = checkReader(readerParam, requestUrl);
|
||||||
if (!checkReader) {
|
if (!checkReader) {
|
||||||
returnJson(response,"请求重复",500);
|
returnJson(response, "请求重复", 500);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (!sm3Check(request)) {
|
/*if (!sm3Check(request)) {
|
||||||
returnJson(response,"请求参数丢失",500);
|
returnJson(response,"请求参数丢失",500);
|
||||||
return false;
|
return false;
|
||||||
|
}*/
|
||||||
|
if (!checkIsYq(request, requestWrapper)) {
|
||||||
|
returnJson(response, "请求越权,请检查用户权限", 500);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void returnJson(HttpServletResponse response,String msg,int code){
|
private void returnJson(HttpServletResponse response, String msg, int code) {
|
||||||
PrintWriter writer=null;
|
PrintWriter writer = null;
|
||||||
response.setCharacterEncoding("UTF-8");
|
response.setCharacterEncoding("UTF-8");
|
||||||
response.setContentType("applicatiopn/json;charset=utf-8");
|
response.setContentType("applicatiopn/json;charset=utf-8");
|
||||||
AjaxResult a=AjaxResult.error(code,msg);
|
AjaxResult a = AjaxResult.error(code, msg);
|
||||||
String res=JSON.toJSONString(a);
|
String res = JSON.toJSONString(a);
|
||||||
try {
|
try {
|
||||||
writer=response.getWriter();
|
writer = response.getWriter();
|
||||||
writer.println(res);
|
writer.println(res);
|
||||||
}catch (IOException e){
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否越权
|
||||||
|
*/
|
||||||
|
private boolean checkIsYq(HttpServletRequest request, XssRequestWrapper requestWrapper) throws Exception {
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
String[] headUrls = requestURI.split("/");
|
||||||
|
String url = "/" + headUrls[1] + "/" + headUrls[2];
|
||||||
|
Boolean result = true;
|
||||||
|
// String token = requestWrapper.getParameter("token");
|
||||||
|
String token = SecurityUtils.getToken(request);
|
||||||
|
if (StringUtils.isNotEmpty(token)) {
|
||||||
|
|
||||||
|
// String userId = JwtUtils.getIscUserId(token);
|
||||||
|
String userId = JwtUtils.getUserId(token);
|
||||||
|
System.out.println("拦截器userId:" + userId);
|
||||||
|
if (StringUtil.isEmpty(userId)) {
|
||||||
|
result = false;
|
||||||
|
} else {
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
if (loginUser != null && loginUser.getSysUser() != null) {
|
||||||
|
if(CollectionUtils.isNotEmpty(loginUser.getSysUser().getMenus())){
|
||||||
|
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// return false;
|
||||||
|
}
|
||||||
|
// result = resourceService.hasPermitURLObj(userId, "9b4483c383538275018615493e1451ea", url);
|
||||||
|
}
|
||||||
|
System.out.println("==================越狱记录:========================userId:" + userId + "============是否越狱:" + result);
|
||||||
|
} else {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
addExceedsAccessLog(url, token);
|
||||||
|
return false;
|
||||||
|
//添加弹框
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void addExceedsAccessLog(String url, String token) {
|
private void addExceedsAccessLog(String url, String token) {
|
||||||
SysOperLog sysOperLog = new SysOperLog();
|
SysLog sysLog = new SysLog();
|
||||||
sysOperLog.setGrade("越权访问");
|
String id = UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
sysOperLog.setOperName(JwtUtils.getUserName(token));
|
sysLog.setLogId(id);
|
||||||
sysOperLog.setTimes(DateTimeHelper.getNowTime());
|
sysLog.setUserId(Long.valueOf(JwtUtils.getUserId(token)));
|
||||||
sysOperLog.setRoleName("继远管理员");
|
sysLog.setOperaUserName(JwtUtils.getUserName(token));
|
||||||
sysOperLog.setDeptName("建设分公司");
|
sysLog.setOperTime(DateTimeHelper.getNowTime());
|
||||||
sysOperLog.setOperIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
|
sysLog.setIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
|
||||||
UrlEnums[] enums = UrlEnums.values();
|
UrlEnums[] enums = UrlEnums.values();
|
||||||
for (UrlEnums anEnum : enums) {
|
for (UrlEnums anEnum : enums) {
|
||||||
if (url.startsWith(anEnum.getUrl())) {
|
if (url.startsWith(anEnum.getUrl())) {
|
||||||
sysOperLog.setTitle(anEnum.getInfo());
|
sysLog.setModel(anEnum.getInfo());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(sysOperLog.getTitle())) {
|
sysLog.setLogType(2);
|
||||||
sysOperLog.setTitle("系统管理");
|
sysLog.setOperUri(url);
|
||||||
}
|
sysLog.setFailureReason("用户越权访问地址");
|
||||||
sysOperLog.setRequestMethod("");
|
sysLog.setGrade("高");
|
||||||
sysOperLog.setMethod("");
|
sysLog.setErrType("越权访问");
|
||||||
sysOperLog.setBusinessType(1);
|
sysLog.setResult(1);
|
||||||
sysOperLog.setOperUrl("");
|
remoteLogService.saveSysLog(sysLog, SecurityConstants.INNER);
|
||||||
sysOperLog.setOperParam("");
|
|
||||||
sysOperLog.setDetail("用户越权访问地址:" + url);
|
|
||||||
sysOperLog.setLogType("系统日志");
|
|
||||||
sysOperLog.setSysMenu("");
|
|
||||||
sysOperLog.setStatus(1);
|
|
||||||
remoteLogService.saveLogs(sysOperLog, SecurityConstants.INNER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -192,9 +217,9 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
*/
|
*/
|
||||||
private boolean sm3Check(HttpServletRequest request) {
|
private boolean sm3Check(HttpServletRequest request) {
|
||||||
Map<String, String> map = new LinkedHashMap<>();
|
Map<String, String> map = new LinkedHashMap<>();
|
||||||
String tok="token";
|
String tok = "token";
|
||||||
request.getParameterMap().forEach((key, value) -> {
|
request.getParameterMap().forEach((key, value) -> {
|
||||||
if (!Objects.equals(key, tok) ) {
|
if (!Objects.equals(key, tok)) {
|
||||||
map.put(key, String.join(" ", value));
|
map.put(key, String.join(" ", value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -289,7 +314,7 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
}
|
}
|
||||||
list.add(newRnd);
|
list.add(newRnd);
|
||||||
requestLogMap.put(currentRequest, list);
|
requestLogMap.put(currentRequest, list);
|
||||||
String brute="requestLogMap";
|
String brute = "requestLogMap";
|
||||||
if (session.getAttribute(brute) != null) {
|
if (session.getAttribute(brute) != null) {
|
||||||
session.removeAttribute(brute);
|
session.removeAttribute(brute);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@ com.securitycontrol.common.security.service.TokenService
|
||||||
com.securitycontrol.common.security.aspect.PreAuthorizeAspect
|
com.securitycontrol.common.security.aspect.PreAuthorizeAspect
|
||||||
com.securitycontrol.common.security.aspect.InnerAuthAspect
|
com.securitycontrol.common.security.aspect.InnerAuthAspect
|
||||||
com.securitycontrol.common.security.handler.GlobalExceptionHandler
|
com.securitycontrol.common.security.handler.GlobalExceptionHandler
|
||||||
|
com.securitycontrol.common.security.interceptor.MyFilter
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@
|
||||||
select DISTINCT sm.menu_auth
|
select DISTINCT sm.menu_auth
|
||||||
FROM sys_user su
|
FROM sys_user su
|
||||||
left join sys_role_menu srm on srm .role_id=su.role_id
|
left join sys_role_menu srm on srm .role_id=su.role_id
|
||||||
left join sys_menu sm on sm.menu_id=srm.menu_id and sm.menu_type=1 AND sm.del_flag=0
|
left join sys_menu sm on sm.menu_id=srm.menu_id and sm.menu_type=2 AND sm.del_flag=0
|
||||||
where su.user_id=#{userId}
|
where su.user_id=#{userId}
|
||||||
</select>
|
</select>
|
||||||
<select id="getAllMenuList" resultType="com.securitycontrol.system.api.domain.decision.SysMenu">
|
<select id="getAllMenuList" resultType="com.securitycontrol.system.api.domain.decision.SysMenu">
|
||||||
select sm.menu_url url,sm.menu_id menuId,sm.menu_name menuName,sm.menu_logo
|
select sm.menu_url url,sm.menu_id menuId,sm.menu_name menuName,sm.menu_logo
|
||||||
FROM sys_user su
|
FROM sys_user su
|
||||||
left join sys_role_menu srm on srm .role_id=su.role_id
|
left join sys_role_menu srm on srm .role_id=su.role_id
|
||||||
left join sys_menu sm on sm.menu_id=srm.menu_id and sm.del_flag=0 and sm.menu_type=0
|
left join sys_menu sm on sm.menu_id=srm.menu_id and sm.del_flag=0 and (sm.menu_type=0 or sm.menu_type=1)
|
||||||
where su.user_id=#{userId} and sm.p_id=#{pid}
|
where su.user_id=#{userId} and sm.p_id=#{pid}
|
||||||
ORDER BY sm.menu_sort ASC
|
ORDER BY sm.menu_sort ASC
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue