linux systemctl 指令 设置开机执行自定义脚本


若在linux中,要配置开机执行自定义脚本,有多种方式,但是从实用性跟从考虑复用的角度上来讲,偏向于编辑server服务,并通过linux的systemctl指令来开机启动,同时还可以通过该指令来执行重启、关闭等操作。以下以启动一个frp服务为例

自定义脚本

  1. 启动脚本

    1
    2
    3
    4
    path=/root/frp/frp_0.35.1_linux_amd64
    cd ${path}
    echo "${PWD}"
    nohup ./frps -c ./frps.ini > log.log 2>&1 &
  2. 重启脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    path=/root/frp/frp_0.35.1_linux_amd64
    cd ${path}
    echo "${PWD}"
    port=7000
    pid=$(netstat -nlp | grep :$port | awk '{print $7}' | awk -F"/" '{ print $1 }');
    echo $pid
    #杀掉对应的进程,如果pid不存在,则不执行
    if [ -n "$pid" ]; then
    kill -9 $pid;
    fi

    sh frp_cmd.sh
  3. 关闭脚本

    1
    2
    3
    4
    5
    6
    7
    port=7000
    pid=$(netstat -nlp | grep :$port | awk '{print $7}' | awk -F"/" '{ print $1 }');
    echo $pid
    #杀掉对应的进程,如果pid不存在,则不执行
    if [ -n "$pid" ]; then
    kill -9 $pid;
    fi

编辑server文件

进入/usr/lib/systemd/system目录,编辑frp.service文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=frp
ConditionFileIsExecutable=/root/frp/frp_0.35.1_linux_amd64/frp_cmd.sh
After=network.target

[Service]
Type=forking
ExecStart=/usr/bin/bash /root/frp/frp_0.35.1_linux_amd64/frp_cmd.sh
ExecReload=/usr/bin/bash /root/frp/frp_0.35.1_linux_amd64/reload-frp.sh
ExecStop=/usr/bin/bash /root/frp/frp_0.35.1_linux_amd64/kill-cmd.sh
[Install]
WantedBy=multi-user.target

其中,各个参数的配置可以从网上找到相应的解释,这里不另外写。

加入开机启动

执行systemctl enable frp

查看状态与其他命令

  1. 注意当编辑完service文件,需要执行容器重载命令systemctl daemon-reload;
  2. 使用systemctl status frp命令查看服务状态。
  3. 使用systemctl start/reload/stop frp 命令来控制你的服务。

springboot整合jwt+spring security 实现接口动态权限控


基于springboot + security + jwt,实现接口动态权限控制。关于security jwt

数据库结构准备

需要基础表:_user、_role、_user_role、_permission、_role_permission,其中,用户表结构可以不用这么多,但是角色、权限表那些就需要保持一样,才能够跟配置类对应上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
Navicat Premium Data Transfer

Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50650
Source Host : localhost:3306
Source Schema : hlsd100

Target Server Type : MySQL
Target Server Version : 50650
File Encoding : 65001

Date: 19/11/2021 15:07:47
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for _permission
-- ----------------------------
DROP TABLE IF EXISTS `_permission`;
CREATE TABLE `_permission` (
`permission_id` int(11) NOT NULL AUTO_INCREMENT,
`permission_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`pid` int(11) NOT NULL DEFAULT -1 COMMENT '默认-1,-1代表匿名url,其他代表需要权限。',
PRIMARY KEY (`permission_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of _permission
-- ----------------------------
INSERT INTO `_permission` VALUES (1, '获取用户数组', '/user/**', -1);
INSERT INTO `_permission` VALUES (2, '匿名可访问url', '/swagger-ui.html', -1);
INSERT INTO `_permission` VALUES (3, '匿名可访问url', '/swagger-resources/*', -1);
INSERT INTO `_permission` VALUES (4, '匿名可访问url', '/webjars/*', -1);
INSERT INTO `_permission` VALUES (5, '匿名可访问url', '/*/api-docs', -1);
INSERT INTO `_permission` VALUES (6, '匿名可访问url', '/static/**', -1);
INSERT INTO `_permission` VALUES (7, '匿名可访问url', '/api/auth/**', -1);
INSERT INTO `_permission` VALUES (8, '匿名可访问url', '/error/**', -1);
INSERT INTO `_permission` VALUES (9, '匿名可访问url', '/webSocket/**', -1);
INSERT INTO `_permission` VALUES (10, '匿名可访问url', '/favicon.ico', -1);
INSERT INTO `_permission` VALUES (11, '匿名可访问url', '/auth/**', -1);

-- ----------------------------
-- Table structure for _role
-- ----------------------------
DROP TABLE IF EXISTS `_role`;
CREATE TABLE `_role` (
`role_id` int(11) NOT NULL AUTO_INCREMENT,
`role_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of _role
-- ----------------------------
INSERT INTO `_role` VALUES (1, 'admin');
INSERT INTO `_role` VALUES (2, 'root');
INSERT INTO `_role` VALUES (3, 'user');

-- ----------------------------
-- Table structure for _role_permission
-- ----------------------------
DROP TABLE IF EXISTS `_role_permission`;
CREATE TABLE `_role_permission` (
`role_permission_id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) NULL DEFAULT NULL,
`permission_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`role_permission_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of _role_permission
-- ----------------------------
INSERT INTO `_role_permission` VALUES (1, 1, 1);
INSERT INTO `_role_permission` VALUES (2, 2, 1);

-- ----------------------------
-- Table structure for _user
-- ----------------------------
DROP TABLE IF EXISTS `_user`;
CREATE TABLE `_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`user_gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`user_tel` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`user_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`user_logo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`u_acco` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户名',
`u_pass` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '密码',
`user_upper_a` int(11) NOT NULL DEFAULT 0 COMMENT '上级a,这里指的是代理商。-1代表没绑定,否则表示是代理商的用户id',
`user_balance` int(11) NOT NULL DEFAULT 0 COMMENT '余额,如果提现了需要减去,这个字段普通用户跟代理商都会有,分销的时候是加到这个字段,',
`user_total_balance` int(11) NOT NULL DEFAULT 0 COMMENT ' 总余额,不管有没有提现,都不会减少。-代理商收入跟分销收入都会加到这里',
`user_upper_b` int(11) NOT NULL DEFAULT 0 COMMENT '上级b,这里指的是代理商。-1代表没绑定,否则表示是代理商的用户id',
`createtime` bigint(13) NULL DEFAULT NULL,
`user_sale_amount` int(11) NOT NULL DEFAULT 0 COMMENT ' 销售额度,代理商才会有,普通用户可以不用考虑该字段,代理商收入会添加到这个字段',
`user_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT ' 二维码',
`user_visit` int(11) NOT NULL DEFAULT 0,
`user_version_total` int(11) NOT NULL DEFAULT 0 COMMENT ' ',
`is_agent` int(11) NOT NULL DEFAULT 0,
`last_login_time` bigint(13) NULL DEFAULT NULL,
`user_wx_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`invite_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 517 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of _user
-- ----------------------------
INSERT INTO `_user` VALUES (1, 'admin', NULL, NULL, NULL, NULL, 'admin', 'admin', 0, 0, 0, 0, NULL, 0, NULL, 19, 10, 1, NULL, NULL, NULL);
INSERT INTO `_user` VALUES (516, 'ean', '男', '4008823823', '984960659@qq.com', NULL, 'ean', '123456', 0, 0, 0, 0, NULL, 0, NULL, 0, 0, 0, NULL, NULL, NULL);

-- ----------------------------
-- Table structure for _user_role
-- ----------------------------
DROP TABLE IF EXISTS `_user_role`;
CREATE TABLE `_user_role` (
`user_role_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NULL DEFAULT NULL,
`role_id` int(11) NULL DEFAULT NULL,
`user_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`user_avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`role_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`status` int(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`user_role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

jwt相关的工具类

JwtTokenUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
@Slf4j
@Component
public class JwtTokenUtils implements InitializingBean {

private final JwtSecurityProperties jwtSecurityProperties;
private static final String AUTHORITIES_KEY = "auth";
private Key key;

@Value(("${jwt.base64-secret}"))
private String base64Secret;

public JwtTokenUtils(JwtSecurityProperties jwtSecurityProperties) {
this.jwtSecurityProperties = jwtSecurityProperties;
}


@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(base64Secret);
this.key = Keys.hmacShaKeyFor(keyBytes);
}

/*登陆的时候调用这个函数生成token,并记录用户信息以及角色,这个claims参数必须要有roles*/
public String createToken(Map<String, Object> claims) {
String roles = String.valueOf(claims.get("roles"));
String ip = String.valueOf(claims.get("ip"));
if (null == roles || "".equals(roles)) {
roles = "user";
}
Date date = new Date();
return Jwts.builder()
.claim(AUTHORITIES_KEY, claims)
.claim("ROLE", roles)
.claim("IP", ip)
.setId(UUID.randomUUID().toString())
.setIssuedAt(date)
.setExpiration(new Date(date.getTime() + jwtSecurityProperties.getTokenValidityInSeconds()))
.compressWith(CompressionCodecs.DEFLATE)
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}

public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}

/**
* @Author Ean
* @Description 解析token,返回token的角色数组
* @Date 2021/11/18
*/
public Authentication getAuthentication(String token, HttpServletRequest httpServletRequest) {
if ("Yz78r4DSn3yZrSgT".equals(token)) {//处理携带特殊token的情况
return new UsernamePasswordAuthenticationToken("", token, null);
}
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
String ip = String.valueOf(claims.get("IP"));
try {
String remoteIp = IpUtils.getIpAddr(httpServletRequest);
if (!remoteIp.equals(ip)) {
log.error("本次请求IP跟token所携带的IP不一致");
throw new Exception("本次请求IP跟token所携带的IP不一致");
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
// log.info("ROLE->" + claims.get("ROLE"));
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get("ROLE").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

HashMap map = (HashMap) claims.get("auth");

User principal = new User(map.get("username").toString(), map.get("password").toString(), authorities);

return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}

/*校验token格式是否正确*/
public boolean validateToken(String authToken, HttpServletRequest httpServletRequest) {
try {
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(authToken)
.getBody();
String ip = String.valueOf(claims.get("IP"));
try {
String remoteIp = IpUtils.getIpAddr(httpServletRequest);
if (!remoteIp.equals(ip)) {
log.error("本次请求IP跟token所携带的IP不一致");
throw new Exception("本次请求IP跟token所携带的IP不一致");
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.error("Invalid JWT signature.");
// e.printStackTrace();
} catch (ExpiredJwtException e) {
log.error("Expired JWT token.");
// e.printStackTrace();
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token.");
// e.printStackTrace();
} catch (IllegalArgumentException e) {
log.error("JWT token compact of handler are invalid.");
// e.printStackTrace();
}
return false;
}

private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}

IpUtils ip获取工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class IpUtils {
public static String getIpAddr(HttpServletRequest request) throws Exception {
if (request == null) {
throw (new Exception("getIpAddr method HttpServletRequest Object is null"));
}
String ipString = request.getHeader("x-forwarded-for");
if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
ipString = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
ipString = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
ipString = request.getRemoteAddr();
}

// 多个路由时,取第一个非unknown的ip
final String[] arr = ipString.split(",");
for (final String str : arr) {
if (!"unknown".equalsIgnoreCase(str)) {
ipString = str;
break;
}
}
return ipString;
}
}

yml配置文件

1
2
3
4
5
6
7
8
9
#jwt
jwt:
header: Authorization
# 令牌前缀
token-start-with: Bearer
# 使用Base64对该令牌进行编码
base64-secret: 44af5348f21c5ea9d9f80ccc5bddfbf3beebc794b9a4a6258573a841170475a03a03bbd44851882faa0eb2a929ecb0c2e1df12498dafb2764aac83d9d3f5d82d
# 令牌过期时间 此处单位/毫秒-现在是20分钟
token-validity-in-seconds: 1200000

spring security相关java类

JwtAccessDeniedHandler

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @Author Ean
* @Description 没有授权则通过此类返回信息
* @Date 2021/11/18
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}

JwtAuthenticationEntryPoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @Author Ean
* @Description 没有权限则通过此类返回信息
* @Date 2021/11/18
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
}
}

UrlAccessDecisionManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* @author Ean
* @version 0.1.0
* @Description: 该类的作用是判断用户是否有权限访问当前的url
* @return
* @date 2020/8/27 18:06
* @since 0.1.0
*/
@Component
public class UrlAccessDecisionManager implements AccessDecisionManager {

/**
* @param authentication: 当前登录用户的角色信息
* @param object: 请求url信息
* @param collection: `UrlFilterInvocationSecurityMetadataSource`中的getAttributes方法传来的,表示当前请求需要的角色(可能有多个)
* @return: void
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> collection) throws AccessDeniedException, AuthenticationException {
if (null != authentication && authentication.getCredentials().equals("Yz78r4DSn3yZrSgT")) {//跳过有这个token的请求
return;
}
// 遍历角色
for (ConfigAttribute ca : collection) {
// ① 当前url请求需要的权限
String needRole = ca.getAttribute();
if (Constants.ROLE_LOGIN.equals(needRole)) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new BadCredentialsException("未登录!");
} else {
throw new AccessDeniedException("未授权该url!");
}
}

// ② 当前用户所具有的角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
// 只要包含其中一个角色即可访问
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("无权访问该接口");
}

@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}

@Override
public boolean supports(Class<?> aClass) {
return true;
}
}

UrlAccessDeniedHandler

1
2
3
4
5
6
7
8
@Component
public class UrlAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType("application/json");
response.getOutputStream().print(JSONObject.toJSONString(ResultUtil.error(403, "User does not have permission")));
}
}

UrlFilterInvocationSecurityMetadataSource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
* @author Ean
* @version 0.1.0
* @Description: 这个类也比较重要。重写的是security的接口权限判断方法。
* getAttributes函数会去数据库查询某个url所需要的权限
* @return
* @date 2020/8/27 18:07
* @since 0.1.0
*/
@Component
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

@Autowired
IPermissionService iPermissionService;
@Autowired
IRolePermissionService iRolePermissionService;
@Autowired
IRoleService iRoleService;
AntPathMatcher antPathMatcher = new AntPathMatcher();

/***
* 返回该url所需要的用户权限信息
*
* @param object: 储存请求url信息
* @return: null:标识不需要任何权限都可以访问
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 获取当前请求url
String requestUrl = ((FilterInvocation) object).getRequestUrl();
/*放行匿名URL*/
if (includeUrl(requestUrl)) {
return null;
}
// 数据库中所有url
Permission[] list = iPermissionService.list(new QueryWrapper<Permission>().ne("pid", -1)).toArray(new Permission[0]);
for (Permission permission : list) {
if (null != permission && antPathMatcher.match(permission.getUrl(), requestUrl)) {
RolePermission[] rolePermissions = iRolePermissionService.list(new QueryWrapper<RolePermission>().eq("permission_id", permission.getPermissionId())).toArray(new RolePermission[0]);
String[] roles = new String[rolePermissions.length];
int i = 0;
if (rolePermissions.length != 0) {
Role role;
for (RolePermission rolePermission : rolePermissions) {
role = iRoleService.getById(rolePermission.getRoleId());
roles[i++] = role.getRoleTitle();
}
}
if (roles.length == 0) {
roles = new String[]{Constants.ROLE_LOGIN};
}
// 保存该url对应角色权限信息
return SecurityConfig.createList(roles);
}
}

// 如果数据中没有找到相应url资源则为非法访问,要求用户登录再进行操作
String[] roleArr = {"user"};//默认角色都是user
return SecurityConfig.createList(roleArr);
}

private boolean includeUrl(String requestUrl) {
AntPathMatcher antPathMatcher = new AntPathMatcher();
boolean flag;
for (Permission permission : WebSecurityConfig.getANNO_URL_LIST()) {
flag = antPathMatcher.match(permission.getUrl(), requestUrl);
if (flag) {
return true;
}
}
return false;
}

@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}

@Override
public boolean supports(Class<?> aClass) {
return FilterInvocation.class.isAssignableFrom(aClass);
}
}

过滤器配置

WebSecurityConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

/**
* @Author Ean
* @Description 总过滤器
* @Date 2021/11/18
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtTokenUtils jwtTokenUtils;
public static List<Permission> ANNO_URL_LIST = new ArrayList();

/*获取匿名可访问数组*/
public static List<Permission> getANNO_URL_LIST() {
if (ANNO_URL_LIST.size() <= 0) {
IPermissionService iPermissionService = (IPermissionService) SpringContextUtil.getBean(IPermissionService.class);
ANNO_URL_LIST = iPermissionService.list(new QueryWrapper<Permission>().eq("pid", -1));
}
return ANNO_URL_LIST;
}

/*更新匿名可访问数组*/
public static void flushANNO_URL_LIST() {
IPermissionService iPermissionService = (IPermissionService) SpringContextUtil.getBean(IPermissionService.class);
ANNO_URL_LIST = iPermissionService.list(new QueryWrapper<Permission>().eq("pid", -1));
}

/**
* 获取访问url所需要的角色信息
*/
private final UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;
/**
* 认证权限处理 - 将上面所获得角色权限与当前登录用户的角色做对比,如果包含其中一个角色即可正常访问
*/
private final UrlAccessDecisionManager urlAccessDecisionManager;
/**
* 自定义访问无权限接口时403响应内容
*/
private final UrlAccessDeniedHandler urlAccessDeniedHandler;

public WebSecurityConfig(JwtAccessDeniedHandler jwtAccessDeniedHandler, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtTokenUtils jwtTokenUtils, UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource, UrlAccessDecisionManager urlAccessDecisionManager, UrlAccessDeniedHandler urlAccessDeniedHandler) {

this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtTokenUtils = jwtTokenUtils;
this.urlFilterInvocationSecurityMetadataSource = urlFilterInvocationSecurityMetadataSource;
this.urlAccessDecisionManager = urlAccessDecisionManager;
this.urlAccessDeniedHandler = urlAccessDeniedHandler;
}

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors().and()
// 禁用 CSRF
.csrf().disable()
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 所有请求都需要认证
.anyRequest().authenticated().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(urlAccessDecisionManager);
return o;
}
});
;

// 登录过后访问无权限的接口时自定义403响应内容
httpSecurity.exceptionHandling().accessDeniedHandler(urlAccessDeniedHandler);
;
// 登录过后访问无权限的接口时自定义403响应内容
httpSecurity.exceptionHandling().accessDeniedHandler(urlAccessDeniedHandler);
// 禁用缓存
httpSecurity.headers().cacheControl();
// 添加JWT filter
httpSecurity.apply(new TokenConfigurer(jwtTokenUtils));

}

public static class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

private final JwtTokenUtils jwtTokenUtils;

public TokenConfigurer(JwtTokenUtils jwtTokenUtils) {
this.jwtTokenUtils = jwtTokenUtils;
}

@Override
public void configure(HttpSecurity http) {
JwtAuthenticationTokenFilter customFilter = new JwtAuthenticationTokenFilter(jwtTokenUtils);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}

}

CorsConfigure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* @Author Ean
* @Description 跨域配置
* @Date 2021/11/18
*/
@Configuration
public class CorsConfigure {

@Bean
public CorsConfigurationSource corsConfigurationSource() {
MySecurityProperties mySecurityProperties = new MySecurityProperties();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(mySecurityProperties.corsAllowedHeaders);
corsConfiguration.setAllowedMethods(mySecurityProperties.corsAllowedMethods);
corsConfiguration.setAllowedOrigins(mySecurityProperties.corsAllowedOrigins);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(mySecurityProperties.corsPath, corsConfiguration);
return source;
}

private class MySecurityProperties {

/*允许跨域的资源路径*/
private String corsPath;

/*允许跨域的请求头*/
private List<String> corsAllowedHeaders;

/*允许跨域的方法*/
private List<String> corsAllowedMethods;

/*允许跨的站点*/
private List<String> corsAllowedOrigins;

private final List<String> CORS_ALLOWED_HEADERS = Arrays.asList("Authorization", "Content-type");
private final List<String> CORS_ALLOWED_METHODS = Arrays.asList("GET", "POST", "PUT", "DELETE");

public MySecurityProperties() {
this.corsPath = "/**";
this.corsAllowedHeaders = CORS_ALLOWED_HEADERS;
this.corsAllowedMethods = CORS_ALLOWED_METHODS;
this.corsAllowedOrigins = Arrays.asList("*");
}
}
}

JwtAuthenticationTokenFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* @author Ean
* @version 0.1.0
* @Description: 重点过滤器,该过滤器拦截请求,并校验token。通过则提取角色信息交给security处理
* @return
* @date 2020/8/27 18:05
* @since 0.1.0
*/
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

private JwtTokenUtils jwtTokenUtils;

public JwtAuthenticationTokenFilter(JwtTokenUtils jwtTokenUtils) {
this.jwtTokenUtils = jwtTokenUtils;
}

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
JwtSecurityProperties jwtSecurityProperties = (JwtSecurityProperties) SpringContextUtil.getBean(JwtSecurityProperties.class);
String requestRri = httpServletRequest.getRequestURI();
//获取request token
String token = null;
String bearerToken = httpServletRequest.getHeader(jwtSecurityProperties.getHeader());
if (null!=bearerToken&&bearerToken.equals("Yz78r4DSn3yZrSgT")) {//忽略带有此token的请求
Authentication authentication = jwtTokenUtils.getAuthentication(bearerToken, httpServletRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
}

if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(jwtSecurityProperties.getTokenStartWith())) {
token = bearerToken.substring(jwtSecurityProperties.getTokenStartWith().length());
}

/*访问接口的时候,判断token是否有效并从token里边提取用户信息跟角色记录到此次请求中*/
if (StringUtils.hasText(token) && jwtTokenUtils.validateToken(token,httpServletRequest)) {
Authentication authentication = jwtTokenUtils.getAuthentication(token,httpServletRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestRri);
} else {
log.debug("no valid JWT token found, uri: {}", requestRri);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);

}
}

授权控制器

AuthController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Slf4j
@RestController
@RequestMapping("/api/auth")
@Api(tags = "系统授权接口")
public class AuthController {

private final JwtTokenUtils jwtTokenUtils;
final
IPermissionService iPermissionService;
final
IUserService userService;

public AuthController(JwtTokenUtils jwtTokenUtils, IPermissionService iPermissionService, IUserService userService) {
this.jwtTokenUtils = jwtTokenUtils;
this.iPermissionService = iPermissionService;
this.userService = userService;
}

@ApiOperation("登录授权")
@PostMapping(value = "/login")
public Result login(String uAcco, String uPass, HttpServletRequest httpServletRequest) {
Map resMap = userService.checkAndGetToken(uAcco, uPass,httpServletRequest);
if (null != resMap) {
return ResultUtil.success(resMap);
}
return ResultUtil.error(-1, "用户名密码错误");
}

@ApiOperation("刷新匿名url")
@GetMapping("flushANNO_URL")
public String flushANNO_URL() {
IPermissionService iPermissionService = (IPermissionService) SpringContextUtil.getBean(IPermissionService.class);
List<Permission> ANNO_URL_LIST = iPermissionService.list(new QueryWrapper<Permission>().eq("pid", -1));
WebSecurityConfig.ANNO_URL_LIST = ANNO_URL_LIST;
return "flush ok";
}
}

checkAndGetToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public Map checkAndGetToken(String uAcco, String uPass, HttpServletRequest servletRequest) {
String ip="";
try {
ip = IpUtils.getIpAddr(servletRequest);
} catch (Exception e) {
e.printStackTrace();
}
User user = this.getOne(new QueryWrapper<User>().eq("u_acco", uAcco).eq("u_pass", uPass));
if (null != user) {
Map param = new HashMap();
param.put("username", uAcco);
param.put("password", uPass);
param.put("ip", ip);
String[] roleTitle = roleMapper.getRoleTitle(user.getUserId());
if (roleTitle.length > 0) {
param.put("roles", StringUtils.join(roleTitle, ","));
}
Map res = new HashMap();
res.put("Authorization", jwtTokenUtils.createToken(param));
res.put("user", user);
return res;
}
return null;
}

pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--Security框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.6</version>
</dependency>

ffmpeg常用命令


视频剪切

1
ffmpeg -i a.avi -ss 00:05:56 -to 00:21:26 -c copy b.avi

视频格式转换

1
ffmpeg -i input.mp4 output.avi

视频提取音频

1
ffmpeg -i test.mp4 -f mp3 -vn test.mp3

关于禅道的数据迁移


  1. 需要确保新旧服务器的禅道版本一致。
  2. 备份旧服务器禅道上的以下内容
  • /opt/zbox/app/zentao/www/data/upload/1
  • /opt/zbox/data/mysql/zentao
  1. 需要修改端口,必须使用禅道命令,zbox -ap[-mp] xx

java类转mysql表创建语句


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package cn.eangaie.cloud.wx3562;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

import static java.lang.Character.isUpperCase;

public class EntityToTable {

static List<String> typeList = new ArrayList<>();

public static void main(String[] args) throws ClassNotFoundException {
EntityToTable entityToTable = new EntityToTable();
String packageTitle = "cn.eangaie.cloud.wx3562.entity.";
String packagePath = "E:\\workspace\\kehu\\3562\\wx3562-backend\\cloud-wx3562\\src\\main\\java\\cn\\eangaie\\cloud\\wx3562\\entity";
List<String> tableArr = getTableArr(packagePath);
for (String tb : tableArr) {
String sql = entityToTable.generateTableMysql(packageTitle + tb, true);
Console.log(sql);
}
Console.log("typeList", typeList);
}

private static List<String> getTableArr(String packagePath) {
return FileUtil.listFileNames(packagePath).stream().map(o -> o.substring(0, o.length() - 5)).collect(Collectors.toList());
}

private static final Map<String, String> dataTypeMap = new HashMap<>();

static {
// 映射Java数据类型到MySQL字段数据类型
dataTypeMap.put("java.lang.String", "VARCHAR(255)");
dataTypeMap.put("int", "INT");
dataTypeMap.put("java.lang.Integer", "INT");
dataTypeMap.put("long", "BIGINT");
dataTypeMap.put("java.lang.Long", "BIGINT");
dataTypeMap.put("float", "FLOAT");
dataTypeMap.put("java.lang.Float", "FLOAT");
dataTypeMap.put("double", "DOUBLE");
dataTypeMap.put("java.lang.Double", "DOUBLE");
dataTypeMap.put("boolean", "BOOLEAN");
dataTypeMap.put("java.lang.Boolean", "BOOLEAN");
dataTypeMap.put("java.math.BigDecimal", "DECIMAL(10, 2)"); // 10是总位数,2是小数位数,根据实际需求调整
// 其他数据类型的映射可以根据需要添加
}

public String generateTableMysql(String beanName, boolean isConvert) {
StringBuilder sqlSb = new StringBuilder();
if (null != beanName && !"".equals(beanName)) {
Object obj = null;
try {
obj = Class.forName(beanName).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
// 拿到该类
Class<?> clz = obj.getClass();
// 获取实体类的所有属性,返回Field数组
Field[] fields = clz.getDeclaredFields();
//获取实体类的Table注解表名
TableName tableClass;
tableClass = clz.getAnnotation(TableName.class);
String tableName = tableClass.value();
sqlSb.append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" ( ");
for (Field field : fields) {
if (!field.isAccessible()) field.setAccessible(true);
String type = field.getType().getTypeName();
if (!type.contains(".") || type.contains("java.")) {
if (!field.getType().equals(List.class)) {
//字段名称
String name = field.getName();
if ("serialVersionUID".equals(name)) continue;
StringBuilder convertName = new StringBuilder();
convertName.append(name);
//如果需要转换驼峰格式
if (isConvert) {
convertName = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
//如果是大写前面先加一个_
if (isUpperCase(name.charAt(i))) convertName.append("_");
convertName.append(name.charAt(i));
}
}
name = convertName.toString().toLowerCase(Locale.ROOT);
sqlSb.append(name).append(" ");
//java 数据类型转换成 MySQL 字段数据类型
String societySqlType = societyMysql(type);
sqlSb.append(societySqlType);
intFiledFilter(name, societySqlType, sqlSb);
//判断该字段是否是Id主键
if (field.isAnnotationPresent(TableId.class)) {
if ("int".equals(type)) sqlSb.append("AUTO_INCREMENT ");
sqlSb.append("PRIMARY KEY ");
}
sqlSb.append("comment ");
//字段属性说明
if (field.isAnnotationPresent(ApiModelProperty.class)) {
ApiModelProperty explain = field.getAnnotation(ApiModelProperty.class);
sqlSb.append("'").append(explain.value()).append("',");
} else {
sqlSb.append("'',");
}
}
}
}
if (sqlSb.lastIndexOf(",") == sqlSb.length() - 1) sqlSb = sqlSb.deleteCharAt(sqlSb.length() - 1);
sqlSb.append(");");
}
return sqlSb.toString();
}

private void intFiledFilter(String name, String societySqlType, StringBuilder sqlSb) {
if ("INT".equals(societySqlType)) {
if (name.contains("state") || name.contains("status") || name.contains("flag")|| name.contains("is_")) sqlSb.append("(1)");
}
sqlSb.append(" ");
}

private String societyMysql(String type) {
if (!typeList.contains(type)) typeList.add(type);
String mySQLDataType = dataTypeMap.get(type);
if (mySQLDataType == null) mySQLDataType = "VARCHAR(255)";
return mySQLDataType;
}

}