Merge remote-tracking branch 'origin/kako' into kako
正在显示
26 个修改的文件
包含
504 行增加
和
101 行删除
| @@ -6,11 +6,16 @@ server: | @@ -6,11 +6,16 @@ server: | ||
| 6 | port: 8066 | 6 | port: 8066 |
| 7 | servlet: | 7 | servlet: |
| 8 | context-path: ${SERVER_CONTEXTPATH:} | 8 | context-path: ${SERVER_CONTEXTPATH:} |
| 9 | + tomcat: | ||
| 10 | + connection-timeout: 20000 # 20秒 | ||
| 11 | + max-threads: 200 | ||
| 12 | + accept-count: 100 | ||
| 13 | + max-connections: 8192 | ||
| 9 | spring: | 14 | spring: |
| 10 | devtools: | 15 | devtools: |
| 11 | add-properties: false | 16 | add-properties: false |
| 12 | profiles: | 17 | profiles: |
| 13 | - active: dev | 18 | + active: prd |
| 14 | mvc: | 19 | mvc: |
| 15 | #静态资源,设置上传文件的访问, | 20 | #静态资源,设置上传文件的访问, |
| 16 | static-path-pattern: /** | 21 | static-path-pattern: /** |
| @@ -28,6 +33,7 @@ spring: | @@ -28,6 +33,7 @@ spring: | ||
| 28 | # host: 127.0.0.1 | 33 | # host: 127.0.0.1 |
| 29 | host: 192.168.1.53 | 34 | host: 192.168.1.53 |
| 30 | port: 6379 | 35 | port: 6379 |
| 36 | + password: vmvnv1v2VV. | ||
| 31 | jackson: | 37 | jackson: |
| 32 | serialization: | 38 | serialization: |
| 33 | FAIL_ON_EMPTY_BEANS: false | 39 | FAIL_ON_EMPTY_BEANS: false |
| @@ -50,7 +56,7 @@ spring: | @@ -50,7 +56,7 @@ spring: | ||
| 50 | enabled: true | 56 | enabled: true |
| 51 | 57 | ||
| 52 | datasource: | 58 | datasource: |
| 53 | - type: com.alibaba.druid.pool.DruidDataSource | 59 | + type: com.zaxxer.hikari.HikariDataSource |
| 54 | #oracle | 60 | #oracle |
| 55 | #driver-class-name=oracle.jdbc.OracleDriver | 61 | #driver-class-name=oracle.jdbc.OracleDriver |
| 56 | #url=jdbc:oracle:thin:@10.50.3.68:1521:CGODW | 62 | #url=jdbc:oracle:thin:@10.50.3.68:1521:CGODW |
| @@ -61,30 +67,26 @@ spring: | @@ -61,30 +67,26 @@ spring: | ||
| 61 | username: 110 | 67 | username: 110 |
| 62 | password: QAHqCJf2kFYCLirM | 68 | password: QAHqCJf2kFYCLirM |
| 63 | driver-class-name: com.mysql.cj.jdbc.Driver | 69 | driver-class-name: com.mysql.cj.jdbc.Driver |
| 64 | - #配置初始化大小/最小/最大 | ||
| 65 | - druid: | ||
| 66 | - initial-size: 1 | ||
| 67 | - min-idle: 1 | ||
| 68 | - max-active: 20 | ||
| 69 | - #获取连接等待超时时间 | ||
| 70 | - max-wait: 60000 | ||
| 71 | - #一个连接在池中最小生存的时间 | ||
| 72 | - min-evictable-idle-time-millis: 300000 | ||
| 73 | - #间隔多久进行一次检测,检测需要关闭的空闲连接 | ||
| 74 | - time-between-eviction-runs-millis: 60000 | ||
| 75 | - #mysql | ||
| 76 | - validation-query: SELECT 1 FROM DUAL | ||
| 77 | - #oracle | ||
| 78 | - #druid.validation-query:SELECT 'x' FROM DUAL | ||
| 79 | - test-while-idle: true | ||
| 80 | - test-on-borrow: false | ||
| 81 | - test-on-return: false | ||
| 82 | - default-auto-commit: true | ||
| 83 | -# tomcat: | ||
| 84 | -# max-wait: 10 | ||
| 85 | -# max-idle: 10000 | ||
| 86 | -# min-idle: 5 | ||
| 87 | -# initial-size: 5 | 70 | + # HikariCP特有配置 |
| 71 | + hikari: | ||
| 72 | + # 连接池名称 | ||
| 73 | + pool-name: KakoUserHikariCP- | ||
| 74 | + # 最小空闲连接数 | ||
| 75 | + minimum-idle: 1 | ||
| 76 | + # 最大连接数 | ||
| 77 | + maximum-pool-size: 20 | ||
| 78 | + # 连接超时时间(毫秒) | ||
| 79 | + connection-timeout: 60000 | ||
| 80 | + # 空闲连接超时时间(毫秒) | ||
| 81 | + idle-timeout: 300000 | ||
| 82 | + # 连接测试超时时间 | ||
| 83 | + validation-timeout: 3000 | ||
| 84 | + # 连接最大生命周期 | ||
| 85 | + max-lifetime: 1800000 | ||
| 86 | + # 连接测试查询 | ||
| 87 | + connection-test-query: SELECT 1 | ||
| 88 | + # 初始化连接数 | ||
| 89 | + initialization-fail-timeout: 1 | ||
| 88 | 90 | ||
| 89 | eureka: | 91 | eureka: |
| 90 | instance: | 92 | instance: |
| @@ -95,11 +97,7 @@ eureka: | @@ -95,11 +97,7 @@ eureka: | ||
| 95 | hostname: ${spring.cloud.client.ip-address} | 97 | hostname: ${spring.cloud.client.ip-address} |
| 96 | lease-renewal-interval-in-seconds: 15 | 98 | lease-renewal-interval-in-seconds: 15 |
| 97 | lease-expiration-duration-in-seconds: 45 | 99 | lease-expiration-duration-in-seconds: 45 |
| 98 | - health-check-url-path: /actuator/health | ||
| 99 | - metadata-map: | ||
| 100 | - user: | ||
| 101 | - name: "admin" #These two are needed so that the server | ||
| 102 | - password: "123456" | 100 | + health-check-url-path: /ac/health |
| 103 | client: | 101 | client: |
| 104 | #eureka注册中心服务器地址 | 102 | #eureka注册中心服务器地址 |
| 105 | service-url: | 103 | service-url: |
| @@ -107,19 +105,27 @@ eureka: | @@ -107,19 +105,27 @@ eureka: | ||
| 107 | defaultZone: http://192.168.1.53:12345/eureka/ | 105 | defaultZone: http://192.168.1.53:12345/eureka/ |
| 108 | registry-fetch-interval-seconds: 30 | 106 | registry-fetch-interval-seconds: 30 |
| 109 | 107 | ||
| 110 | - | ||
| 111 | - | ||
| 112 | management: | 108 | management: |
| 113 | endpoints: | 109 | endpoints: |
| 114 | enabled-by-default: true | 110 | enabled-by-default: true |
| 115 | web: | 111 | web: |
| 112 | + base-path: /ac # 端点路径更改为 /ac | ||
| 116 | exposure: | 113 | exposure: |
| 117 | - include: "*" | 114 | + include: "health,info" # 仅暴露必要端点 |
| 118 | endpoint: | 115 | endpoint: |
| 119 | health: | 116 | health: |
| 120 | - show-details: always | 117 | + show-details: never # 不显示详细健康信息 |
| 121 | shutdown: | 118 | shutdown: |
| 122 | - enabled: true | 119 | + enabled: false # 禁用关闭端点 |
| 120 | + env: | ||
| 121 | + enabled: false # 禁用环境端点(防止敏感信息泄露) | ||
| 122 | + mappings: | ||
| 123 | + enabled: false # 禁用映射端点(防止接口路径泄露) | ||
| 124 | + beans: | ||
| 125 | + enabled: false # 禁用beans端点 | ||
| 126 | + configprops: | ||
| 127 | + enabled: false # 禁用configprops端点 | ||
| 128 | + | ||
| 123 | 129 | ||
| 124 | mybatis: | 130 | mybatis: |
| 125 | mapper-locations: classpath:mapping/*.xml | 131 | mapper-locations: classpath:mapping/*.xml |
| @@ -184,7 +184,7 @@ | @@ -184,7 +184,7 @@ | ||
| 184 | </springProfile> | 184 | </springProfile> |
| 185 | 185 | ||
| 186 | <!--生产环境:输出到文件--> | 186 | <!--生产环境:输出到文件--> |
| 187 | - <springProfile name="pro"> | 187 | + <springProfile name="prd"> |
| 188 | <root level="info"> | 188 | <root level="info"> |
| 189 | <appender-ref ref="CONSOLE" /> | 189 | <appender-ref ref="CONSOLE" /> |
| 190 | <appender-ref ref="DEBUG_FILE" /> | 190 | <appender-ref ref="DEBUG_FILE" /> |
| @@ -194,4 +194,4 @@ | @@ -194,4 +194,4 @@ | ||
| 194 | </root> | 194 | </root> |
| 195 | </springProfile> | 195 | </springProfile> |
| 196 | 196 | ||
| 197 | -</configuration> | ||
| 197 | +</configuration> |
| @@ -4,9 +4,11 @@ VOLUME /tmp | @@ -4,9 +4,11 @@ VOLUME /tmp | ||
| 4 | 4 | ||
| 5 | ADD *.jar app.jar | 5 | ADD *.jar app.jar |
| 6 | 6 | ||
| 7 | -EXPOSE 8066 | 7 | +EXPOSE 8067 |
| 8 | +# Ubuntu 时区 同步主机与docker容器时间 | ||
| 9 | +RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime | ||
| 10 | +RUN echo "Asia/Shanghai" > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata | ||
| 11 | + | ||
| 12 | +ENTRYPOINT ["java","-Xms128m", "-Xmx256m","-jar","/app.jar"] | ||
| 8 | 13 | ||
| 9 | -ENTRYPOINT ["java","-jar","/app.jar"] | ||
| 10 | 14 | ||
| 11 | -# Ubuntu 时区 同步主机与docker容器时间 | ||
| 12 | -RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone |
| @@ -11,9 +11,9 @@ | @@ -11,9 +11,9 @@ | ||
| 11 | </parent> | 11 | </parent> |
| 12 | <groupId>com.tianbo</groupId> | 12 | <groupId>com.tianbo</groupId> |
| 13 | <artifactId>usercenter</artifactId> | 13 | <artifactId>usercenter</artifactId> |
| 14 | - <version>2.3Kako</version> | 14 | + <version>2.3.8Kako</version> |
| 15 | <name>usercenter</name> | 15 | <name>usercenter</name> |
| 16 | - <description>usercenter for springcloud</description> | 16 | + <description>usercenter for kako system</description> |
| 17 | 17 | ||
| 18 | <properties> | 18 | <properties> |
| 19 | <java.version>1.8</java.version> | 19 | <java.version>1.8</java.version> |
| @@ -169,12 +169,6 @@ | @@ -169,12 +169,6 @@ | ||
| 169 | <version>${fastjson_version}</version> | 169 | <version>${fastjson_version}</version> |
| 170 | </dependency> | 170 | </dependency> |
| 171 | <!-- tools--> | 171 | <!-- tools--> |
| 172 | - <!-- alibaba的druid数据库连接池 --> | ||
| 173 | - <dependency> | ||
| 174 | - <groupId>com.alibaba</groupId> | ||
| 175 | - <artifactId>druid-spring-boot-starter</artifactId> | ||
| 176 | - <version>1.1.9</version> | ||
| 177 | - </dependency> | ||
| 178 | <!-- mysql--> | 172 | <!-- mysql--> |
| 179 | <dependency> | 173 | <dependency> |
| 180 | <groupId>mysql</groupId> | 174 | <groupId>mysql</groupId> |
| @@ -2,6 +2,7 @@ package com.tianbo.warehouse; | @@ -2,6 +2,7 @@ package com.tianbo.warehouse; | ||
| 2 | 2 | ||
| 3 | import org.springframework.context.annotation.Bean; | 3 | import org.springframework.context.annotation.Bean; |
| 4 | import org.springframework.context.annotation.Configuration; | 4 | import org.springframework.context.annotation.Configuration; |
| 5 | +import org.springframework.context.annotation.Profile; | ||
| 5 | import springfox.documentation.builders.ApiInfoBuilder; | 6 | import springfox.documentation.builders.ApiInfoBuilder; |
| 6 | import springfox.documentation.builders.PathSelectors; | 7 | import springfox.documentation.builders.PathSelectors; |
| 7 | import springfox.documentation.builders.RequestHandlerSelectors; | 8 | import springfox.documentation.builders.RequestHandlerSelectors; |
| @@ -13,6 +14,7 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2; | @@ -13,6 +14,7 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2; | ||
| 13 | 14 | ||
| 14 | @Configuration | 15 | @Configuration |
| 15 | @EnableSwagger2 | 16 | @EnableSwagger2 |
| 17 | +@Profile({"dev", "test"}) // 只在dev和test环境启用 | ||
| 16 | public class Swagger2 { | 18 | public class Swagger2 { |
| 17 | //swagger2的配置文件,这里可以配置swagger2的一些基本的内容,比如扫描的包等等 | 19 | //swagger2的配置文件,这里可以配置swagger2的一些基本的内容,比如扫描的包等等 |
| 18 | @Bean | 20 | @Bean |
| 1 | package com.tianbo.warehouse.annotation.cache.util.redis; | 1 | package com.tianbo.warehouse.annotation.cache.util.redis; |
| 2 | 2 | ||
| 3 | -import com.alibaba.druid.util.StringUtils; | 3 | + |
| 4 | import lombok.extern.slf4j.Slf4j; | 4 | import lombok.extern.slf4j.Slf4j; |
| 5 | 5 | ||
| 6 | import org.aspectj.lang.ProceedingJoinPoint; | 6 | import org.aspectj.lang.ProceedingJoinPoint; |
| @@ -10,6 +10,7 @@ import org.springframework.expression.EvaluationContext; | @@ -10,6 +10,7 @@ import org.springframework.expression.EvaluationContext; | ||
| 10 | import org.springframework.expression.spel.standard.SpelExpressionParser; | 10 | import org.springframework.expression.spel.standard.SpelExpressionParser; |
| 11 | import org.springframework.expression.spel.support.StandardEvaluationContext; | 11 | import org.springframework.expression.spel.support.StandardEvaluationContext; |
| 12 | import org.springframework.stereotype.Component; | 12 | import org.springframework.stereotype.Component; |
| 13 | +import org.springframework.util.StringUtils; | ||
| 13 | 14 | ||
| 14 | import java.util.Arrays; | 15 | import java.util.Arrays; |
| 15 | 16 |
| 1 | +package com.tianbo.warehouse.controller; | ||
| 2 | + | ||
| 3 | +import com.alibaba.fastjson.JSON; | ||
| 4 | +import com.alibaba.fastjson.JSONObject; | ||
| 5 | +import com.tianbo.warehouse.controller.response.ResultJson; | ||
| 6 | +import com.tianbo.warehouse.dao.KakoUserMapper; | ||
| 7 | +import com.tianbo.warehouse.model.KakoUser; | ||
| 8 | +import com.tianbo.warehouse.util.RedisUtils; | ||
| 9 | +import io.swagger.annotations.ApiOperation; | ||
| 10 | +import lombok.extern.slf4j.Slf4j; | ||
| 11 | +import org.apache.commons.lang.StringUtils; | ||
| 12 | +import org.springframework.beans.factory.annotation.Autowired; | ||
| 13 | +import org.springframework.web.bind.annotation.PostMapping; | ||
| 14 | +import org.springframework.web.bind.annotation.RestController; | ||
| 15 | + | ||
| 16 | +import javax.annotation.Resource; | ||
| 17 | +import javax.servlet.http.HttpServletRequest; | ||
| 18 | +import javax.servlet.http.HttpServletResponse; | ||
| 19 | +import java.util.Date; | ||
| 20 | + | ||
| 21 | +@RestController() | ||
| 22 | +@Slf4j | ||
| 23 | +public class HeartBeatController { | ||
| 24 | + | ||
| 25 | + @Autowired | ||
| 26 | + private RedisUtils redisUtils; | ||
| 27 | + | ||
| 28 | + @Resource | ||
| 29 | + private KakoUserMapper kakoUserMapper; | ||
| 30 | + | ||
| 31 | + //token头部标识类型,Bearer代表Bearer TOKEN | ||
| 32 | + static final String AUTHORIZATION_HEADER = "Bearer "; | ||
| 33 | + | ||
| 34 | + //检查token时效是否低于标准线 | ||
| 35 | + static final long TOKEN_TTL_CHECK_MIN= 36000L; | ||
| 36 | + | ||
| 37 | + //重置token时效为标准线 | ||
| 38 | + static final long TOKEN_TTL_ADD= 86400L; | ||
| 39 | + | ||
| 40 | + //心跳每次续费时长 | ||
| 41 | + static final long HEARTBEAT_TOKEN_TTL_ADD= 10L; | ||
| 42 | + | ||
| 43 | + @ApiOperation(value = "用户心跳接口", notes = "心跳续期") | ||
| 44 | + @PostMapping("/heartbeat") | ||
| 45 | + public ResultJson heartbeat(HttpServletRequest request, HttpServletResponse response){ | ||
| 46 | + try { | ||
| 47 | + | ||
| 48 | + //1. 获取客户端IP,因为有反向代理所以要从头部获取代理过来的头部IP | ||
| 49 | + String clientIP =null; | ||
| 50 | + clientIP = request.getRemoteAddr(); | ||
| 51 | + String header_forwarded = request.getHeader("x-forwarded-for"); | ||
| 52 | + if (StringUtils.isNotBlank(header_forwarded)) { | ||
| 53 | + clientIP = request.getHeader("x-forwarded-for"); | ||
| 54 | + // 多次反向代理后会有多个ip值,第一个ip才是真实ip | ||
| 55 | + if (clientIP.contains(",")) { | ||
| 56 | + clientIP = clientIP.split(",")[0]; | ||
| 57 | + } | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + //2.获取token | ||
| 61 | + String token = request.getHeader("Authorization"); | ||
| 62 | + /** | ||
| 63 | + * key样式 | ||
| 64 | + * accessToken:token | ||
| 65 | + */ | ||
| 66 | + if (token!=null && !token.isEmpty() && token.startsWith(AUTHORIZATION_HEADER)){ | ||
| 67 | + token = token.substring(AUTHORIZATION_HEADER.length()); | ||
| 68 | + String accessToken = token; | ||
| 69 | + String userDetailStr = redisUtils.get(accessToken); | ||
| 70 | + | ||
| 71 | + | ||
| 72 | + //4. 更新用户心跳时间及在线状态IP等资料 | ||
| 73 | + if (StringUtils.isNotBlank(userDetailStr)){ | ||
| 74 | + | ||
| 75 | + JSONObject u = JSON.parseObject(userDetailStr); | ||
| 76 | + String userId= u.getString("id"); | ||
| 77 | + String userInfo = u.getString("name"); | ||
| 78 | + String username = u.getString("username"); | ||
| 79 | +// userDetailStr = userDetailStr.replace("@",""); | ||
| 80 | + | ||
| 81 | + /**3.续期token过期时间 | ||
| 82 | + * 增加过期时间,考虑到程序及网络传输中间的时间损耗, | ||
| 83 | + * 每10秒一个的心跳直接续费10秒的话,token的过期时间还是会随着时间逐步减少 | ||
| 84 | + */ | ||
| 85 | + long tokenExpireTime= redisUtils.getExpire(accessToken); | ||
| 86 | + if(tokenExpireTime < TOKEN_TTL_CHECK_MIN){ | ||
| 87 | + redisUtils.expire(accessToken, TOKEN_TTL_ADD); | ||
| 88 | + redisUtils.expire(username, TOKEN_TTL_ADD); | ||
| 89 | + }else{ | ||
| 90 | + redisUtils.expire(accessToken,tokenExpireTime+HEARTBEAT_TOKEN_TTL_ADD); | ||
| 91 | + redisUtils.expire(username, tokenExpireTime+HEARTBEAT_TOKEN_TTL_ADD); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + /** | ||
| 95 | + * 多式联运用户表 | ||
| 96 | + */ | ||
| 97 | +// Integer dsly_userId = u.getInteger("id"); | ||
| 98 | +// USER user = new USER(); | ||
| 99 | +// user.setId(dsly_userId); | ||
| 100 | +// user.setLoginip(clientIP); | ||
| 101 | +// user.setLogintime(new Date()); | ||
| 102 | +// user.setOnline(true); | ||
| 103 | +// int ii = userMapper.updateByPrimaryKeySelective(user); | ||
| 104 | + | ||
| 105 | + KakoUser kakoUser = new KakoUser(); | ||
| 106 | + kakoUser.setId(userId); | ||
| 107 | + kakoUser.setLoginIp(clientIP); | ||
| 108 | + kakoUser.setLoginDate(new Date()); | ||
| 109 | + kakoUser.setOnline(true); | ||
| 110 | + int i = kakoUserMapper.updateByPrimaryKeySelective(kakoUser); | ||
| 111 | + | ||
| 112 | + return i > 0 ? new ResultJson("200","心跳成功"): new ResultJson("400","心跳失败"); | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + } | ||
| 116 | + return new ResultJson("400","心跳失败"); | ||
| 117 | + | ||
| 118 | + }catch (Exception e){ | ||
| 119 | + log.error("[HEART-BEAT-ERROR]-",e); | ||
| 120 | + return new ResultJson("400","心跳失败"); | ||
| 121 | + } | ||
| 122 | + } | ||
| 123 | +} |
| @@ -46,7 +46,7 @@ public class PermssionController { | @@ -46,7 +46,7 @@ public class PermssionController { | ||
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | @LogAnnotation(moduleName = "权限管理",operate = "权限修改") | 48 | @LogAnnotation(moduleName = "权限管理",operate = "权限修改") |
| 49 | - @PutMapping("/edit") | 49 | + @PostMapping("/edit") |
| 50 | @ResponseBody | 50 | @ResponseBody |
| 51 | public ResultJson edit(@RequestBody @Valid PERMISSION permission){ | 51 | public ResultJson edit(@RequestBody @Valid PERMISSION permission){ |
| 52 | 52 | ||
| @@ -62,7 +62,7 @@ public class PermssionController { | @@ -62,7 +62,7 @@ public class PermssionController { | ||
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | @LogAnnotation(moduleName = "权限管理",operate = "权限删除") | 64 | @LogAnnotation(moduleName = "权限管理",operate = "权限删除") |
| 65 | - @DeleteMapping("/del") | 65 | + @PostMapping("/del") |
| 66 | public ResultJson reomve(@RequestBody PERMISSION permission, HttpServletRequest request, HttpServletResponse response){ | 66 | public ResultJson reomve(@RequestBody PERMISSION permission, HttpServletRequest request, HttpServletResponse response){ |
| 67 | 67 | ||
| 68 | int i =permissionService.deleteByPrimaryKey(permission.getPermissionId().toString()); | 68 | int i =permissionService.deleteByPrimaryKey(permission.getPermissionId().toString()); |
| @@ -47,14 +47,14 @@ public class RoleController { | @@ -47,14 +47,14 @@ public class RoleController { | ||
| 47 | * @return | 47 | * @return |
| 48 | */ | 48 | */ |
| 49 | @LogAnnotation(moduleName = "岗位/角色管理",operate = "权限设置") | 49 | @LogAnnotation(moduleName = "岗位/角色管理",operate = "权限设置") |
| 50 | - @PutMapping("/permSet") | 50 | + @PostMapping("/permSet") |
| 51 | public ResultJson permissionSet(@RequestBody RolePermission rolePermission){ | 51 | public ResultJson permissionSet(@RequestBody RolePermission rolePermission){ |
| 52 | int i = roleService.setRolePermissoin(rolePermission); | 52 | int i = roleService.setRolePermissoin(rolePermission); |
| 53 | return i==1 ? new ResultJson("200","设置权限成功") :new ResultJson("500","设置权限失败"); | 53 | return i==1 ? new ResultJson("200","设置权限成功") :new ResultJson("500","设置权限失败"); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | @LogAnnotation(moduleName = "岗位/角色管理",operate = "岗位/角色修改") | 56 | @LogAnnotation(moduleName = "岗位/角色管理",operate = "岗位/角色修改") |
| 57 | - @PutMapping("/edit") | 57 | + @PostMapping("/edit") |
| 58 | @ResponseBody | 58 | @ResponseBody |
| 59 | public ResultJson edit(@RequestBody @Valid ROLE role){ | 59 | public ResultJson edit(@RequestBody @Valid ROLE role){ |
| 60 | 60 | ||
| @@ -70,7 +70,7 @@ public class RoleController { | @@ -70,7 +70,7 @@ public class RoleController { | ||
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | @LogAnnotation(moduleName = "岗位/角色管理",operate = "岗位/角色删除") | 72 | @LogAnnotation(moduleName = "岗位/角色管理",operate = "岗位/角色删除") |
| 73 | - @DeleteMapping("/del") | 73 | + @PostMapping("/del") |
| 74 | public ResultJson reomve(@RequestBody ROLE role, HttpServletRequest request, HttpServletResponse response){ | 74 | public ResultJson reomve(@RequestBody ROLE role, HttpServletRequest request, HttpServletResponse response){ |
| 75 | 75 | ||
| 76 | int i =roleService.deleteByPrimaryKey(role.getRoleId()); | 76 | int i =roleService.deleteByPrimaryKey(role.getRoleId()); |
| @@ -26,6 +26,7 @@ import io.swagger.annotations.ApiImplicitParam; | @@ -26,6 +26,7 @@ import io.swagger.annotations.ApiImplicitParam; | ||
| 26 | import io.swagger.annotations.ApiImplicitParams; | 26 | import io.swagger.annotations.ApiImplicitParams; |
| 27 | import io.swagger.annotations.ApiOperation; | 27 | import io.swagger.annotations.ApiOperation; |
| 28 | import lombok.extern.slf4j.Slf4j; | 28 | import lombok.extern.slf4j.Slf4j; |
| 29 | +import org.apache.commons.lang.StringUtils; | ||
| 29 | import org.springframework.beans.factory.annotation.Autowired; | 30 | import org.springframework.beans.factory.annotation.Autowired; |
| 30 | import org.springframework.context.annotation.Bean; | 31 | import org.springframework.context.annotation.Bean; |
| 31 | import org.springframework.security.core.context.SecurityContextHolder; | 32 | import org.springframework.security.core.context.SecurityContextHolder; |
| @@ -71,10 +72,16 @@ public class UserController { | @@ -71,10 +72,16 @@ public class UserController { | ||
| 71 | @RequestParam(value = "pageSize",required = false,defaultValue = "5") | 72 | @RequestParam(value = "pageSize",required = false,defaultValue = "5") |
| 72 | int pageSize, | 73 | int pageSize, |
| 73 | @RequestParam(value = "userName",required = false) String username, | 74 | @RequestParam(value = "userName",required = false) String username, |
| 74 | - @RequestParam(value = "realName",required = false) String realname) | 75 | + @RequestParam(value = "realName",required = false) String realname, |
| 76 | + @RequestParam(value = "online",required = false) Boolean online, | ||
| 77 | + @RequestParam(value = "loginFlag",required = false) String loginFlag) | ||
| 75 | { | 78 | { |
| 76 | - | ||
| 77 | - PageInfo<KakoUser> usersPageInfo = userService.selectAllUser(pageNum,pageSize, username, realname); | 79 | + KakoUser kakoUser = new KakoUser(); |
| 80 | + kakoUser.setLoginFlag(loginFlag); | ||
| 81 | + kakoUser.setLoginName(username); | ||
| 82 | + kakoUser.setName(realname); | ||
| 83 | + kakoUser.setOnline(online); | ||
| 84 | + PageInfo<KakoUser> usersPageInfo = userService.selectAllUser(pageNum,pageSize, kakoUser); | ||
| 78 | return new ResultJson("200","success",usersPageInfo); | 85 | return new ResultJson("200","success",usersPageInfo); |
| 79 | } | 86 | } |
| 80 | 87 | ||
| @@ -86,17 +93,36 @@ public class UserController { | @@ -86,17 +93,36 @@ public class UserController { | ||
| 86 | } | 93 | } |
| 87 | 94 | ||
| 88 | @LogAnnotation(moduleName = "用户管理",operate = "用户编辑") | 95 | @LogAnnotation(moduleName = "用户管理",operate = "用户编辑") |
| 89 | - @PutMapping("/edit") | 96 | + @PostMapping("/edit") |
| 90 | public ResultJson updateUserById(@Validated(UpdateUser.class) @RequestBody KakoUser user){ | 97 | public ResultJson updateUserById(@Validated(UpdateUser.class) @RequestBody KakoUser user){ |
| 91 | user.setPassword(null); | 98 | user.setPassword(null); |
| 92 | int i = userService.updateByPrimaryKeySelective(user); | 99 | int i = userService.updateByPrimaryKeySelective(user); |
| 93 | - ResultJson resultJson = new ResultJson(); | ||
| 94 | return i==1 ? new ResultJson("200","success") :new ResultJson("500","update faild"); | 100 | return i==1 ? new ResultJson("200","success") :new ResultJson("500","update faild"); |
| 95 | 101 | ||
| 96 | } | 102 | } |
| 97 | 103 | ||
| 104 | + @LogAnnotation(moduleName = "用户锁定",operate = "用户锁定") | ||
| 105 | + @PostMapping("/lock") | ||
| 106 | + public ResultJson lockUserById(@Validated(UpdateUser.class) @RequestBody KakoUser user){ | ||
| 107 | + KakoUser kakoUser = new KakoUser(); | ||
| 108 | + kakoUser.setId(user.getId()); | ||
| 109 | + kakoUser.setLoginFlag(user.getLoginFlag()); | ||
| 110 | + | ||
| 111 | + int i = userService.updateByPrimaryKeySelective(kakoUser); | ||
| 112 | + //删除用户token缓存 及时生效锁定账号 | ||
| 113 | + if (i>0){ | ||
| 114 | + String userTokenStr = redisUtils.get(KakoUser.TOKEN_KEY + user.getLoginName()); | ||
| 115 | + if (StringUtils.isNotEmpty(userTokenStr)){ | ||
| 116 | + redisUtils.del(userTokenStr); | ||
| 117 | + } | ||
| 118 | + log.info("用户锁定成功:key:{}",userTokenStr); | ||
| 119 | + } | ||
| 120 | + return i==1 ? new ResultJson("200","success") :new ResultJson("500","lock user faild"); | ||
| 121 | + | ||
| 122 | + } | ||
| 123 | + | ||
| 98 | @LogAnnotation(moduleName = "用户管理",operate = "用户密码修改") | 124 | @LogAnnotation(moduleName = "用户管理",operate = "用户密码修改") |
| 99 | - @PutMapping("/password") | 125 | + @PostMapping("/password") |
| 100 | public ResultJson updateUserPassById(@RequestBody KakoUser user){ | 126 | public ResultJson updateUserPassById(@RequestBody KakoUser user){ |
| 101 | KakoUser kakoUser = new KakoUser(); | 127 | KakoUser kakoUser = new KakoUser(); |
| 102 | kakoUser.setId(user.getId()); | 128 | kakoUser.setId(user.getId()); |
| @@ -114,21 +140,19 @@ public class UserController { | @@ -114,21 +140,19 @@ public class UserController { | ||
| 114 | } | 140 | } |
| 115 | 141 | ||
| 116 | int i = userService.insertSelective(user); | 142 | int i = userService.insertSelective(user); |
| 117 | - ResultJson resultJson = new ResultJson(); | ||
| 118 | return i==1 ? new ResultJson("200","新建账户成功") :new ResultJson("500","insert faild"); | 143 | return i==1 ? new ResultJson("200","新建账户成功") :new ResultJson("500","insert faild"); |
| 119 | 144 | ||
| 120 | } | 145 | } |
| 121 | 146 | ||
| 122 | @LogAnnotation(moduleName = "用户管理",operate = "用户删除") | 147 | @LogAnnotation(moduleName = "用户管理",operate = "用户删除") |
| 123 | - @DeleteMapping("/del") | 148 | + @PostMapping("/del") |
| 124 | public ResultJson delUser(@RequestBody KakoUser user,HttpServletRequest request,HttpServletResponse response){ | 149 | public ResultJson delUser(@RequestBody KakoUser user,HttpServletRequest request,HttpServletResponse response){ |
| 125 | //String username = getusername(); | 150 | //String username = getusername(); |
| 126 | int i = userService.deleteByPrimaryKey(user); | 151 | int i = userService.deleteByPrimaryKey(user); |
| 127 | - ResultJson resultJson = new ResultJson(); | ||
| 128 | return i==1 ? new ResultJson("200","删除账户成功") :new ResultJson("500","delete faild"); | 152 | return i==1 ? new ResultJson("200","删除账户成功") :new ResultJson("500","delete faild"); |
| 129 | } | 153 | } |
| 130 | 154 | ||
| 131 | - @PutMapping("/roleset") | 155 | + @PostMapping("/roleset") |
| 132 | public ResultJson roleSet(@RequestBody Map<String,Object> map,HttpServletRequest request,HttpServletResponse respons){ | 156 | public ResultJson roleSet(@RequestBody Map<String,Object> map,HttpServletRequest request,HttpServletResponse respons){ |
| 133 | String id = map.get("userId").toString(); | 157 | String id = map.get("userId").toString(); |
| 134 | List<Integer> roles = (List<Integer>) map.get("roleIds"); | 158 | List<Integer> roles = (List<Integer>) map.get("roleIds"); |
| @@ -144,7 +168,7 @@ public class UserController { | @@ -144,7 +168,7 @@ public class UserController { | ||
| 144 | */ | 168 | */ |
| 145 | @ApiOperation(value = "更新用户权限缓存", notes = "重新生成用户的信息到redis") | 169 | @ApiOperation(value = "更新用户权限缓存", notes = "重新生成用户的信息到redis") |
| 146 | @RedisCacheDelTarget(cacheKey = "com.tianbo.warehouse.service.imp.PermissionServiceImp") | 170 | @RedisCacheDelTarget(cacheKey = "com.tianbo.warehouse.service.imp.PermissionServiceImp") |
| 147 | - @PutMapping("/resetToken") | 171 | + @PostMapping("/resetToken") |
| 148 | public ResultJson resetToken(HttpServletRequest request,HttpServletResponse respons) { | 172 | public ResultJson resetToken(HttpServletRequest request,HttpServletResponse respons) { |
| 149 | /** | 173 | /** |
| 150 | * 更新目标用户的权限缓存 | 174 | * 更新目标用户的权限缓存 |
| @@ -13,6 +13,7 @@ public interface KakoUserMapper { | @@ -13,6 +13,7 @@ public interface KakoUserMapper { | ||
| 13 | 13 | ||
| 14 | KakoUser selectByPrimaryKey(String id); | 14 | KakoUser selectByPrimaryKey(String id); |
| 15 | 15 | ||
| 16 | + | ||
| 16 | int updateByPrimaryKeySelective(KakoUser record); | 17 | int updateByPrimaryKeySelective(KakoUser record); |
| 17 | 18 | ||
| 18 | int updateByPrimaryKey(KakoUser record); | 19 | int updateByPrimaryKey(KakoUser record); |
| @@ -21,7 +22,9 @@ public interface KakoUserMapper { | @@ -21,7 +22,9 @@ public interface KakoUserMapper { | ||
| 21 | 22 | ||
| 22 | List<KakoUser> selectAllUser(KakoUser record); | 23 | List<KakoUser> selectAllUser(KakoUser record); |
| 23 | 24 | ||
| 25 | + List<KakoUser> selectOnlineUser(); | ||
| 26 | + | ||
| 24 | int lockUser(KakoUser record); | 27 | int lockUser(KakoUser record); |
| 25 | 28 | ||
| 26 | 29 | ||
| 27 | -} | ||
| 30 | +} |
| 1 | +package com.tianbo.warehouse.heatbeat; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +import com.tianbo.warehouse.dao.KakoUserMapper; | ||
| 5 | +import lombok.extern.slf4j.Slf4j; | ||
| 6 | +import org.springframework.stereotype.Component; | ||
| 7 | +import com.tianbo.warehouse.model.KakoUser; | ||
| 8 | + | ||
| 9 | +import javax.annotation.PostConstruct; | ||
| 10 | +import javax.annotation.Resource; | ||
| 11 | +import java.util.Date; | ||
| 12 | + | ||
| 13 | +@Component | ||
| 14 | +@Slf4j | ||
| 15 | +public class OfflineTheardJob implements Runnable { | ||
| 16 | + | ||
| 17 | + private static OfflineTheardJob offlineTheardJob; | ||
| 18 | + | ||
| 19 | + private KakoUser user; | ||
| 20 | + | ||
| 21 | + //用户掉线判定时间差 | ||
| 22 | + static final long OFFLINE_= 60L; | ||
| 23 | + | ||
| 24 | + @Resource | ||
| 25 | + private KakoUserMapper userMapper; | ||
| 26 | + | ||
| 27 | + OfflineTheardJob() { | ||
| 28 | + | ||
| 29 | + } | ||
| 30 | + OfflineTheardJob(KakoUser user) { | ||
| 31 | + this.user = user; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + @PostConstruct | ||
| 35 | + public void init(){ | ||
| 36 | + offlineTheardJob = this; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + @Override | ||
| 40 | + public void run(){ | ||
| 41 | + Date userLoginTime = user.getLoginDate(); | ||
| 42 | + if(userLoginTime!=null){ | ||
| 43 | + long diff= Math.abs(System.currentTimeMillis() - userLoginTime.getTime()); | ||
| 44 | + long s = diff / 1000; | ||
| 45 | + | ||
| 46 | + log.info("[HEAT-BEAT]-用户{}心跳-时间相差{}秒",user.getName(),s); | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + if (s > OFFLINE_){ | ||
| 50 | + setOffline(); | ||
| 51 | + } | ||
| 52 | + }else { | ||
| 53 | + setOffline(); | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + private void setOffline(){ | ||
| 58 | + user.setOnline(false); | ||
| 59 | + int i = offlineTheardJob.userMapper.updateByPrimaryKeySelective(user); | ||
| 60 | + if (i>0){ | ||
| 61 | + log.info("用户id:{},用户名称:{},从IP:{}掉线",user.getId(),user.getName(),user.getLoginIp()); | ||
| 62 | + } | ||
| 63 | + } | ||
| 64 | +} |
| 1 | +package com.tianbo.warehouse.heatbeat; | ||
| 2 | + | ||
| 3 | +import com.tianbo.warehouse.dao.KakoUserMapper; | ||
| 4 | +import com.tianbo.warehouse.model.KakoUser; | ||
| 5 | +import lombok.extern.slf4j.Slf4j; | ||
| 6 | +import org.springframework.scheduling.annotation.Scheduled; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | + | ||
| 9 | +import javax.annotation.Resource; | ||
| 10 | +import java.util.List; | ||
| 11 | +import java.util.concurrent.ThreadPoolExecutor; | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * 清理心跳超时的在线用户,判定为离线 | ||
| 16 | + * @author xyh | ||
| 17 | + * @date | ||
| 18 | + * 记得给用户ID,用户名称,用户心跳时间,用户登录ip,用户在线状态的数据库字段设置索引。 | ||
| 19 | + */ | ||
| 20 | +@Slf4j | ||
| 21 | +@Component | ||
| 22 | +public class OfflineUserTask { | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + @Resource | ||
| 26 | + private KakoUserMapper userMapper; | ||
| 27 | + | ||
| 28 | + @Scheduled(fixedRate = 60000) | ||
| 29 | + private void offlineUserHeartBeat(){ | ||
| 30 | + | ||
| 31 | + //初始化线程池 | ||
| 32 | + ThreadPoolExecutor threadPool = XMLThreadPoolFactory.instance(); | ||
| 33 | + | ||
| 34 | + List<KakoUser> userList = userMapper.selectOnlineUser(); | ||
| 35 | + if (userList!=null && !userList.isEmpty()){ | ||
| 36 | + log.trace("用户掉线判定开始,共需判定{}个在线标识用户",userList.size()); | ||
| 37 | + for (KakoUser user:userList) { | ||
| 38 | + OfflineTheardJob offlineTheardJob = new OfflineTheardJob(user); | ||
| 39 | + threadPool.execute(offlineTheardJob); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + } | ||
| 45 | +} | ||
| 46 | + |
| 1 | +package com.tianbo.warehouse.heatbeat; | ||
| 2 | + | ||
| 3 | +import java.util.ArrayList; | ||
| 4 | +import java.util.Date; | ||
| 5 | +import java.util.Iterator; | ||
| 6 | +import java.util.List; | ||
| 7 | +import java.util.concurrent.ThreadFactory; | ||
| 8 | + | ||
| 9 | +public class XMLThreadFactory implements ThreadFactory { | ||
| 10 | + | ||
| 11 | + private int counter; | ||
| 12 | + private String name; | ||
| 13 | + private List<String> stats; | ||
| 14 | + | ||
| 15 | + public XMLThreadFactory(String name) | ||
| 16 | + { | ||
| 17 | + counter = 1; | ||
| 18 | + this.name = name; | ||
| 19 | + stats = new ArrayList<String>(); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + @Override | ||
| 23 | + public Thread newThread(Runnable runnable) | ||
| 24 | + { | ||
| 25 | + Thread t = new Thread(runnable, name + "-Thread_" + counter); | ||
| 26 | + counter++; | ||
| 27 | + stats.add(String.format("Created thread %d with name %s on %s \n", t.getId(), t.getName(), new Date())); | ||
| 28 | + return t; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + public String getStats() | ||
| 32 | + { | ||
| 33 | + StringBuffer buffer = new StringBuffer(); | ||
| 34 | + Iterator<String> it = stats.iterator(); | ||
| 35 | + while (it.hasNext()) | ||
| 36 | + { | ||
| 37 | + buffer.append(it.next()); | ||
| 38 | + } | ||
| 39 | + return buffer.toString(); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | +} |
| 1 | +package com.tianbo.warehouse.heatbeat; | ||
| 2 | + | ||
| 3 | +import java.util.concurrent.LinkedBlockingQueue; | ||
| 4 | +import java.util.concurrent.ThreadPoolExecutor; | ||
| 5 | +import java.util.concurrent.TimeUnit; | ||
| 6 | + | ||
| 7 | +public class XMLThreadPoolFactory { | ||
| 8 | + | ||
| 9 | + private static ThreadPoolExecutor threadPool; | ||
| 10 | + | ||
| 11 | + public static ThreadPoolExecutor instance(){ | ||
| 12 | + if (threadPool==null){ | ||
| 13 | + XMLThreadFactory xmlThreadFactory = new XMLThreadFactory("heartbeatTask"); | ||
| 14 | + threadPool = new ThreadPoolExecutor(12, 64, | ||
| 15 | + 0L, TimeUnit.MILLISECONDS, | ||
| 16 | + new LinkedBlockingQueue<Runnable>(1024), | ||
| 17 | + xmlThreadFactory, | ||
| 18 | + new ThreadPoolExecutor.AbortPolicy()); | ||
| 19 | + } | ||
| 20 | + return threadPool; | ||
| 21 | + } | ||
| 22 | +} |
| @@ -18,6 +18,8 @@ import java.util.Date; | @@ -18,6 +18,8 @@ import java.util.Date; | ||
| 18 | import java.util.List; | 18 | import java.util.List; |
| 19 | 19 | ||
| 20 | public class KakoUser implements UserDetails { | 20 | public class KakoUser implements UserDetails { |
| 21 | + public static String TOKEN_KEY = "user:"; | ||
| 22 | + | ||
| 21 | private String id; | 23 | private String id; |
| 22 | 24 | ||
| 23 | private String companyId; | 25 | private String companyId; |
| @@ -67,6 +69,10 @@ public class KakoUser implements UserDetails { | @@ -67,6 +69,10 @@ public class KakoUser implements UserDetails { | ||
| 67 | 69 | ||
| 68 | private String token; | 70 | private String token; |
| 69 | 71 | ||
| 72 | + private Boolean online; | ||
| 73 | + | ||
| 74 | + private Date expiryDate; | ||
| 75 | + | ||
| 70 | @JSONField(serialzeFeatures= {SerializerFeature.WriteMapNullValue,SerializerFeature.WriteNullStringAsEmpty}) | 76 | @JSONField(serialzeFeatures= {SerializerFeature.WriteMapNullValue,SerializerFeature.WriteNullStringAsEmpty}) |
| 71 | private List<ROLE> roles; | 77 | private List<ROLE> roles; |
| 72 | 78 | ||
| @@ -269,13 +275,39 @@ public class KakoUser implements UserDetails { | @@ -269,13 +275,39 @@ public class KakoUser implements UserDetails { | ||
| 269 | this.token = token; | 275 | this.token = token; |
| 270 | } | 276 | } |
| 271 | 277 | ||
| 278 | + public Boolean getOnline() { | ||
| 279 | + return online; | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + public void setOnline(Boolean online) { | ||
| 283 | + this.online = online; | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + public Date getExpiryDate() { | ||
| 287 | + return expiryDate; | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + public void setExpiryDate(Date expiryDate) { | ||
| 291 | + this.expiryDate = expiryDate; | ||
| 292 | + } | ||
| 293 | + | ||
| 272 | /** | 294 | /** |
| 273 | * | 295 | * |
| 274 | * @return 账户未过期 | 296 | * @return 账户未过期 |
| 297 | + * 未过期为true,已过期为false | ||
| 275 | */ | 298 | */ |
| 276 | @Override | 299 | @Override |
| 277 | public boolean isAccountNonExpired(){ | 300 | public boolean isAccountNonExpired(){ |
| 278 | - return true; | 301 | + // 无过期时间字段时视为永不过期 |
| 302 | + if(expiryDate == null) { | ||
| 303 | + return true; | ||
| 304 | + }else { | ||
| 305 | + Boolean check = new Date().before(expiryDate); | ||
| 306 | + return check; | ||
| 307 | + } | ||
| 308 | + | ||
| 309 | + // 当前时间在过期时间之前视为未过期 | ||
| 310 | + | ||
| 279 | } | 311 | } |
| 280 | 312 | ||
| 281 | /** | 313 | /** |
| @@ -327,4 +359,4 @@ public class KakoUser implements UserDetails { | @@ -327,4 +359,4 @@ public class KakoUser implements UserDetails { | ||
| 327 | } | 359 | } |
| 328 | return auths; | 360 | return auths; |
| 329 | } | 361 | } |
| 330 | -} | ||
| 362 | +} |
| @@ -7,8 +7,10 @@ import org.apache.commons.logging.Log; | @@ -7,8 +7,10 @@ import org.apache.commons.logging.Log; | ||
| 7 | import org.apache.commons.logging.LogFactory; | 7 | import org.apache.commons.logging.LogFactory; |
| 8 | import org.springframework.beans.factory.annotation.Autowired; | 8 | import org.springframework.beans.factory.annotation.Autowired; |
| 9 | import org.springframework.http.HttpStatus; | 9 | import org.springframework.http.HttpStatus; |
| 10 | +import org.springframework.security.authentication.AccountExpiredException; | ||
| 10 | import org.springframework.security.authentication.BadCredentialsException; | 11 | import org.springframework.security.authentication.BadCredentialsException; |
| 11 | import org.springframework.security.authentication.DisabledException; | 12 | import org.springframework.security.authentication.DisabledException; |
| 13 | +import org.springframework.security.authentication.LockedException; | ||
| 12 | import org.springframework.security.core.AuthenticationException; | 14 | import org.springframework.security.core.AuthenticationException; |
| 13 | import org.springframework.security.core.userdetails.UsernameNotFoundException; | 15 | import org.springframework.security.core.userdetails.UsernameNotFoundException; |
| 14 | import org.springframework.security.web.DefaultRedirectStrategy; | 16 | import org.springframework.security.web.DefaultRedirectStrategy; |
| @@ -69,6 +71,8 @@ public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureH | @@ -69,6 +71,8 @@ public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureH | ||
| 69 | response.sendError(HttpStatus.UNAUTHORIZED.value(),"用户不存在或者密码错误"); | 71 | response.sendError(HttpStatus.UNAUTHORIZED.value(),"用户不存在或者密码错误"); |
| 70 | } else if (exception instanceof DisabledException) { | 72 | } else if (exception instanceof DisabledException) { |
| 71 | response.sendError(HttpStatus.UNAUTHORIZED.value(),"账户被禁用,登录失败,请联系管理员!"); | 73 | response.sendError(HttpStatus.UNAUTHORIZED.value(),"账户被禁用,登录失败,请联系管理员!"); |
| 74 | + } else if (exception instanceof AccountExpiredException) { | ||
| 75 | + response.sendError(HttpStatus.UNAUTHORIZED.value(),"账户已过期,登录失败,请联系管理员!"); | ||
| 72 | } else { | 76 | } else { |
| 73 | response.sendError(HttpStatus.UNAUTHORIZED.value(),"登录失败"); | 77 | response.sendError(HttpStatus.UNAUTHORIZED.value(),"登录失败"); |
| 74 | } | 78 | } |
src/main/java/com/tianbo/warehouse/security/handel/kakologin/MyKakoAuthenticationSuccessHandler.java
| @@ -53,9 +53,11 @@ public class MyKakoAuthenticationSuccessHandler extends SavedRequestAwareAuthent | @@ -53,9 +53,11 @@ public class MyKakoAuthenticationSuccessHandler extends SavedRequestAwareAuthent | ||
| 53 | 53 | ||
| 54 | @Autowired | 54 | @Autowired |
| 55 | RedisUtils redisUtils; | 55 | RedisUtils redisUtils; |
| 56 | + | ||
| 56 | @Override | 57 | @Override |
| 57 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { | 58 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { |
| 58 | logger.info("登录成功"); | 59 | logger.info("登录成功"); |
| 60 | + int expirationSeconds = 3600*24*7; | ||
| 59 | if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){ | 61 | if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){ |
| 60 | //将 authention 信息打包成json格式返回 | 62 | //将 authention 信息打包成json格式返回 |
| 61 | response.setContentType("application/json;charset=UTF-8"); | 63 | response.setContentType("application/json;charset=UTF-8"); |
| @@ -77,7 +79,8 @@ public class MyKakoAuthenticationSuccessHandler extends SavedRequestAwareAuthent | @@ -77,7 +79,8 @@ public class MyKakoAuthenticationSuccessHandler extends SavedRequestAwareAuthent | ||
| 77 | loginedUser.setToken(jwtToken); | 79 | loginedUser.setToken(jwtToken); |
| 78 | //这里将登录成功的[user]对象数据写入redis缓存,KEY为token value为user的JSON对象 | 80 | //这里将登录成功的[user]对象数据写入redis缓存,KEY为token value为user的JSON对象 |
| 79 | String json = JSON.toJSONString(user); | 81 | String json = JSON.toJSONString(user); |
| 80 | - redisUtils.set(jwtToken, json,3600*24*7); | 82 | + redisUtils.set(jwtToken, json,expirationSeconds); |
| 83 | + redisUtils.set(KakoUser.TOKEN_KEY + user.getUsername(),jwtToken,expirationSeconds); | ||
| 81 | Map<String,Object> menuMap = permissionService.getUserMenusKako(user.getId()); | 84 | Map<String,Object> menuMap = permissionService.getUserMenusKako(user.getId()); |
| 82 | //返回用户信息和用户可访问的目录列表 | 85 | //返回用户信息和用户可访问的目录列表 |
| 83 | response.getWriter().write(objectMapper.writeValueAsString(new AuthSuccessResponse(loginedUser,menuMap))); | 86 | response.getWriter().write(objectMapper.writeValueAsString(new AuthSuccessResponse(loginedUser,menuMap))); |
| @@ -11,10 +11,7 @@ import org.apache.shiro.crypto.hash.Hash; | @@ -11,10 +11,7 @@ import org.apache.shiro.crypto.hash.Hash; | ||
| 11 | import org.apache.shiro.crypto.hash.SimpleHash; | 11 | import org.apache.shiro.crypto.hash.SimpleHash; |
| 12 | import org.apache.shiro.util.ByteSource; | 12 | import org.apache.shiro.util.ByteSource; |
| 13 | import org.springframework.beans.factory.annotation.Autowired; | 13 | import org.springframework.beans.factory.annotation.Autowired; |
| 14 | -import org.springframework.security.authentication.AuthenticationProvider; | ||
| 15 | -import org.springframework.security.authentication.BadCredentialsException; | ||
| 16 | -import org.springframework.security.authentication.DisabledException; | ||
| 17 | -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | 14 | +import org.springframework.security.authentication.*; |
| 18 | import org.springframework.security.core.Authentication; | 15 | import org.springframework.security.core.Authentication; |
| 19 | import org.springframework.security.core.AuthenticationException; | 16 | import org.springframework.security.core.AuthenticationException; |
| 20 | import org.springframework.security.core.userdetails.UserDetails; | 17 | import org.springframework.security.core.userdetails.UserDetails; |
| @@ -63,6 +60,11 @@ public class MyLoginAuthenticationProvider extends CodecSupport implements Authe | @@ -63,6 +60,11 @@ public class MyLoginAuthenticationProvider extends CodecSupport implements Authe | ||
| 63 | throw new DisabledException("用户被禁用"); | 60 | throw new DisabledException("用户被禁用"); |
| 64 | } | 61 | } |
| 65 | 62 | ||
| 63 | + // 过期判定 | ||
| 64 | + if (!userInfo.isAccountNonExpired()){ | ||
| 65 | + throw new AccountExpiredException("用户已过期"); | ||
| 66 | + } | ||
| 67 | + | ||
| 66 | //取盐规则 | 68 | //取盐规则 |
| 67 | byte[] salt = PasswordSaltUtils.getSalt16(userInfo.getPassword()); | 69 | byte[] salt = PasswordSaltUtils.getSalt16(userInfo.getPassword()); |
| 68 | //真实密码 | 70 | //真实密码 |
| @@ -13,7 +13,7 @@ public interface KakoUserService { | @@ -13,7 +13,7 @@ public interface KakoUserService { | ||
| 13 | 13 | ||
| 14 | KakoUser loadByUsername(String username); | 14 | KakoUser loadByUsername(String username); |
| 15 | 15 | ||
| 16 | - PageInfo<KakoUser> selectAllUser(int pageNum, int pageSize, String username, String realName); | 16 | + PageInfo<KakoUser> selectAllUser(int pageNum, int pageSize, KakoUser kakoUser); |
| 17 | 17 | ||
| 18 | int insertSelective(KakoUser record); | 18 | int insertSelective(KakoUser record); |
| 19 | 19 |
| @@ -59,11 +59,8 @@ public class KakoUserServiceImpl implements KakoUserService{ | @@ -59,11 +59,8 @@ public class KakoUserServiceImpl implements KakoUserService{ | ||
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | @Override | 61 | @Override |
| 62 | - public PageInfo<KakoUser> selectAllUser(int pageNum, int pageSize,String username, String realName){ | 62 | + public PageInfo<KakoUser> selectAllUser(int pageNum, int pageSize,KakoUser users){ |
| 63 | Page<KakoUser> page = PageHelper.startPage(pageNum,pageSize); | 63 | Page<KakoUser> page = PageHelper.startPage(pageNum,pageSize); |
| 64 | - KakoUser users = new KakoUser(); | ||
| 65 | - users.setLoginName(username); | ||
| 66 | - users.setName(realName); | ||
| 67 | List<KakoUser> list = kakoUserMapper.selectAllUser(users); | 64 | List<KakoUser> list = kakoUserMapper.selectAllUser(users); |
| 68 | for (KakoUser user: list) { | 65 | for (KakoUser user: list) { |
| 69 | // List<PERMISSION> permissionList = permissionMapper.findByUserId(user.getUserId()); | 66 | // List<PERMISSION> permissionList = permissionMapper.findByUserId(user.getUserId()); |
| @@ -23,11 +23,18 @@ | @@ -23,11 +23,18 @@ | ||
| 23 | <result column="update_date" property="updateDate" jdbcType="TIMESTAMP" /> | 23 | <result column="update_date" property="updateDate" jdbcType="TIMESTAMP" /> |
| 24 | <result column="remarks" property="remarks" jdbcType="VARCHAR" /> | 24 | <result column="remarks" property="remarks" jdbcType="VARCHAR" /> |
| 25 | <result column="del_flag" property="delFlag" jdbcType="CHAR" /> | 25 | <result column="del_flag" property="delFlag" jdbcType="CHAR" /> |
| 26 | + <result column="online" property="online" jdbcType="BOOLEAN" /> | ||
| 27 | + <result column="expiry_date" property="expiryDate" jdbcType="TIMESTAMP" /> | ||
| 26 | </resultMap> | 28 | </resultMap> |
| 27 | <sql id="Base_Column_List" > | 29 | <sql id="Base_Column_List" > |
| 28 | id, company_id, office_id, login_name, password, no, name, email, phone, mobile, | 30 | id, company_id, office_id, login_name, password, no, name, email, phone, mobile, |
| 29 | user_type, photo, login_ip, login_date, login_flag, create_by, create_date, update_by, | 31 | user_type, photo, login_ip, login_date, login_flag, create_by, create_date, update_by, |
| 30 | - update_date, remarks, del_flag | 32 | + update_date, remarks, del_flag, online, expiry_date |
| 33 | + </sql> | ||
| 34 | + <sql id="Safe_Column_List"> | ||
| 35 | + id, company_id, office_id, login_name, no, name, email, phone, mobile, | ||
| 36 | + user_type, photo, login_ip, login_date, login_flag, create_by, create_date, update_by, | ||
| 37 | + update_date, remarks, del_flag, online, expiry_date | ||
| 31 | </sql> | 38 | </sql> |
| 32 | <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" > | 39 | <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" > |
| 33 | select | 40 | select |
| @@ -37,16 +44,23 @@ | @@ -37,16 +44,23 @@ | ||
| 37 | </select> | 44 | </select> |
| 38 | <select id="selectAllUser" resultMap="BaseResultMap" parameterType="com.tianbo.warehouse.model.KakoUser" > | 45 | <select id="selectAllUser" resultMap="BaseResultMap" parameterType="com.tianbo.warehouse.model.KakoUser" > |
| 39 | select | 46 | select |
| 40 | - <include refid="Base_Column_List" /> | 47 | + <include refid="Safe_Column_List" /> |
| 41 | from sys_user | 48 | from sys_user |
| 42 | - WHERE 1=1 | ||
| 43 | - and del_flag= '0' | ||
| 44 | - <if test=" loginName != null" > | ||
| 45 | - and login_name like concat('%',#{loginName,jdbcType=VARCHAR},'%') | ||
| 46 | - </if> | ||
| 47 | - <if test="name != null" > | ||
| 48 | - and name like concat('%',#{name,jdbcType=VARCHAR},'%') | ||
| 49 | - </if> | 49 | + <where> |
| 50 | + and del_flag= '0' | ||
| 51 | + <if test=" loginName != null" > | ||
| 52 | + and login_name like concat('%',#{loginName,jdbcType=VARCHAR},'%') | ||
| 53 | + </if> | ||
| 54 | + <if test="name != null" > | ||
| 55 | + and name like concat('%',#{name,jdbcType=VARCHAR},'%') | ||
| 56 | + </if> | ||
| 57 | + <if test="online != null" > | ||
| 58 | + and online = #{online,jdbcType=BOOLEAN} | ||
| 59 | + </if> | ||
| 60 | + <if test="loginFlag != null" > | ||
| 61 | + and login_flag = #{loginFlag,jdbcType=VARCHAR} | ||
| 62 | + </if> | ||
| 63 | + </where> | ||
| 50 | </select> | 64 | </select> |
| 51 | <select id="selectByUsername" resultMap="BaseResultMap" parameterType="java.lang.String" > | 65 | <select id="selectByUsername" resultMap="BaseResultMap" parameterType="java.lang.String" > |
| 52 | select | 66 | select |
| @@ -54,6 +68,14 @@ | @@ -54,6 +68,14 @@ | ||
| 54 | from sys_user | 68 | from sys_user |
| 55 | where login_name = #{login_name,jdbcType=VARCHAR} | 69 | where login_name = #{login_name,jdbcType=VARCHAR} |
| 56 | </select> | 70 | </select> |
| 71 | + | ||
| 72 | + <select id="selectOnlineUser" resultMap="BaseResultMap" > | ||
| 73 | + select | ||
| 74 | + <include refid="Base_Column_List" /> | ||
| 75 | + from sys_user | ||
| 76 | + where online > 0; | ||
| 77 | + </select> | ||
| 78 | + | ||
| 57 | <update id="deleteByPrimaryKey" parameterType="java.lang.String" > | 79 | <update id="deleteByPrimaryKey" parameterType="java.lang.String" > |
| 58 | update sys_user | 80 | update sys_user |
| 59 | set | 81 | set |
| @@ -67,7 +89,7 @@ | @@ -67,7 +89,7 @@ | ||
| 67 | mobile, user_type, photo, | 89 | mobile, user_type, photo, |
| 68 | login_ip, login_date, login_flag, | 90 | login_ip, login_date, login_flag, |
| 69 | create_by, create_date, update_by, | 91 | create_by, create_date, update_by, |
| 70 | - update_date, remarks, del_flag | 92 | + update_date, remarks, del_flag, expiry_date |
| 71 | ) | 93 | ) |
| 72 | values (#{id,jdbcType=VARCHAR}, #{companyId,jdbcType=VARCHAR}, #{officeId,jdbcType=VARCHAR}, | 94 | values (#{id,jdbcType=VARCHAR}, #{companyId,jdbcType=VARCHAR}, #{officeId,jdbcType=VARCHAR}, |
| 73 | #{loginName,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{no,jdbcType=VARCHAR}, | 95 | #{loginName,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{no,jdbcType=VARCHAR}, |
| @@ -75,7 +97,7 @@ | @@ -75,7 +97,7 @@ | ||
| 75 | #{mobile,jdbcType=VARCHAR}, #{userType,jdbcType=CHAR}, #{photo,jdbcType=VARCHAR}, | 97 | #{mobile,jdbcType=VARCHAR}, #{userType,jdbcType=CHAR}, #{photo,jdbcType=VARCHAR}, |
| 76 | #{loginIp,jdbcType=VARCHAR}, #{loginDate,jdbcType=TIMESTAMP}, #{loginFlag,jdbcType=VARCHAR}, | 98 | #{loginIp,jdbcType=VARCHAR}, #{loginDate,jdbcType=TIMESTAMP}, #{loginFlag,jdbcType=VARCHAR}, |
| 77 | #{createBy,jdbcType=VARCHAR}, #{createDate,jdbcType=TIMESTAMP}, #{updateBy,jdbcType=VARCHAR}, | 99 | #{createBy,jdbcType=VARCHAR}, #{createDate,jdbcType=TIMESTAMP}, #{updateBy,jdbcType=VARCHAR}, |
| 78 | - #{updateDate,jdbcType=TIMESTAMP}, #{remarks,jdbcType=VARCHAR}, #{delFlag,jdbcType=CHAR} | 100 | + #{updateDate,jdbcType=TIMESTAMP}, #{remarks,jdbcType=VARCHAR}, #{delFlag,jdbcType=CHAR}, #{expiryDate,jdbcType=TIMESTAMP} |
| 79 | ) | 101 | ) |
| 80 | </insert> | 102 | </insert> |
| 81 | <insert id="insertSelective" parameterType="com.tianbo.warehouse.model.KakoUser" > | 103 | <insert id="insertSelective" parameterType="com.tianbo.warehouse.model.KakoUser" > |
| @@ -144,6 +166,9 @@ | @@ -144,6 +166,9 @@ | ||
| 144 | <if test="delFlag != null" > | 166 | <if test="delFlag != null" > |
| 145 | del_flag, | 167 | del_flag, |
| 146 | </if> | 168 | </if> |
| 169 | + <if test="expiryDate != null" > | ||
| 170 | + expiry_date, | ||
| 171 | + </if> | ||
| 147 | </trim> | 172 | </trim> |
| 148 | <trim prefix="values (" suffix=")" suffixOverrides="," > | 173 | <trim prefix="values (" suffix=")" suffixOverrides="," > |
| 149 | <if test="id != null" > | 174 | <if test="id != null" > |
| @@ -209,6 +234,9 @@ | @@ -209,6 +234,9 @@ | ||
| 209 | <if test="delFlag != null" > | 234 | <if test="delFlag != null" > |
| 210 | #{delFlag,jdbcType=CHAR}, | 235 | #{delFlag,jdbcType=CHAR}, |
| 211 | </if> | 236 | </if> |
| 237 | + <if test="expiryDate != null" > | ||
| 238 | + #{expiryDate,jdbcType=TIMESTAMP}, | ||
| 239 | + </if> | ||
| 212 | </trim> | 240 | </trim> |
| 213 | </insert> | 241 | </insert> |
| 214 | <update id="updateByPrimaryKeySelective" parameterType="com.tianbo.warehouse.model.KakoUser" > | 242 | <update id="updateByPrimaryKeySelective" parameterType="com.tianbo.warehouse.model.KakoUser" > |
| @@ -274,6 +302,12 @@ | @@ -274,6 +302,12 @@ | ||
| 274 | <if test="delFlag != null" > | 302 | <if test="delFlag != null" > |
| 275 | del_flag = #{delFlag,jdbcType=CHAR}, | 303 | del_flag = #{delFlag,jdbcType=CHAR}, |
| 276 | </if> | 304 | </if> |
| 305 | + <if test="online != null" > | ||
| 306 | + online = #{online,jdbcType=BOOLEAN}, | ||
| 307 | + </if> | ||
| 308 | + <if test="expiryDate != null" > | ||
| 309 | + expiry_date = #{expiryDate,jdbcType=TIMESTAMP}, | ||
| 310 | + </if> | ||
| 277 | </set> | 311 | </set> |
| 278 | where id = #{id,jdbcType=VARCHAR} | 312 | where id = #{id,jdbcType=VARCHAR} |
| 279 | </update> | 313 | </update> |
| @@ -298,7 +332,9 @@ | @@ -298,7 +332,9 @@ | ||
| 298 | update_by = #{updateBy,jdbcType=VARCHAR}, | 332 | update_by = #{updateBy,jdbcType=VARCHAR}, |
| 299 | update_date = #{updateDate,jdbcType=TIMESTAMP}, | 333 | update_date = #{updateDate,jdbcType=TIMESTAMP}, |
| 300 | remarks = #{remarks,jdbcType=VARCHAR}, | 334 | remarks = #{remarks,jdbcType=VARCHAR}, |
| 301 | - del_flag = #{delFlag,jdbcType=CHAR} | 335 | + del_flag = #{delFlag,jdbcType=CHAR}, |
| 336 | + online = #{online,jdbcType=BOOLEAN}, | ||
| 337 | + expiry_date = #{expiryDate,jdbcType=TIMESTAMP} | ||
| 302 | where id = #{id,jdbcType=VARCHAR} | 338 | where id = #{id,jdbcType=VARCHAR} |
| 303 | </update> | 339 | </update> |
| 304 | 340 |
| @@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
| 19 | <nav class="navbar navbar-inverse navbar-fixed-top"> | 19 | <nav class="navbar navbar-inverse navbar-fixed-top"> |
| 20 | <div class="container"> | 20 | <div class="container"> |
| 21 | <div class="navbar-header"> | 21 | <div class="navbar-header"> |
| 22 | - <a class="navbar-brand" href="#">Spring Security演示</a> | 22 | + <a class="navbar-brand" href="#">u have no rights</a> |
| 23 | </div> | 23 | </div> |
| 24 | <div id="navbar" class="collapse navbar-collapse"> | 24 | <div id="navbar" class="collapse navbar-collapse"> |
| 25 | <ul class="nav navbar-nav"> | 25 | <ul class="nav navbar-nav"> |
| @@ -33,4 +33,4 @@ | @@ -33,4 +33,4 @@ | ||
| 33 | welcom | 33 | welcom |
| 34 | </div> | 34 | </div> |
| 35 | </body> | 35 | </body> |
| 36 | -</html> | ||
| 36 | +</html> |
| @@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
| 19 | <nav class="navbar navbar-inverse navbar-fixed-top"> | 19 | <nav class="navbar navbar-inverse navbar-fixed-top"> |
| 20 | <div class="container"> | 20 | <div class="container"> |
| 21 | <div class="navbar-header"> | 21 | <div class="navbar-header"> |
| 22 | - <a class="navbar-brand" href="#">Spring Security演示</a> | 22 | + <a class="navbar-brand" href="#">user center</a> |
| 23 | </div> | 23 | </div> |
| 24 | <div id="navbar" class="collapse navbar-collapse"> | 24 | <div id="navbar" class="collapse navbar-collapse"> |
| 25 | <ul class="nav navbar-nav"> | 25 | <ul class="nav navbar-nav"> |
| @@ -33,4 +33,4 @@ | @@ -33,4 +33,4 @@ | ||
| 33 | 33 | ||
| 34 | </div> | 34 | </div> |
| 35 | </body> | 35 | </body> |
| 36 | -</html> | ||
| 36 | +</html> |
| @@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
| 19 | <nav class="navbar navbar-inverse navbar-fixed-top"> | 19 | <nav class="navbar navbar-inverse navbar-fixed-top"> |
| 20 | <div class="container"> | 20 | <div class="container"> |
| 21 | <div class="navbar-header"> | 21 | <div class="navbar-header"> |
| 22 | - <a class="navbar-brand" href="#">Spring Security演示</a> | 22 | + <a class="navbar-brand" href="#">user center</a> |
| 23 | </div> | 23 | </div> |
| 24 | <div id="navbar" class="collapse navbar-collapse"> | 24 | <div id="navbar" class="collapse navbar-collapse"> |
| 25 | <ul class="nav navbar-nav"> | 25 | <ul class="nav navbar-nav"> |
| @@ -54,4 +54,4 @@ | @@ -54,4 +54,4 @@ | ||
| 54 | 54 | ||
| 55 | 55 | ||
| 56 | </body> | 56 | </body> |
| 57 | -</html> | ||
| 57 | +</html> |
| @@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
| 19 | <nav class="navbar navbar-inverse navbar-fixed-top"> | 19 | <nav class="navbar navbar-inverse navbar-fixed-top"> |
| 20 | <div class="container"> | 20 | <div class="container"> |
| 21 | <div class="navbar-header"> | 21 | <div class="navbar-header"> |
| 22 | - <a class="navbar-brand" href="#">Spring Security演示</a> | 22 | + <a class="navbar-brand" href="#">user center</a> |
| 23 | </div> | 23 | </div> |
| 24 | <div id="navbar" class="collapse navbar-collapse"> | 24 | <div id="navbar" class="collapse navbar-collapse"> |
| 25 | <ul class="nav navbar-nav"> | 25 | <ul class="nav navbar-nav"> |
| @@ -53,4 +53,4 @@ | @@ -53,4 +53,4 @@ | ||
| 53 | </div> | 53 | </div> |
| 54 | </div> | 54 | </div> |
| 55 | </body> | 55 | </body> |
| 56 | -</html> | ||
| 56 | +</html> |
-
请 注册 或 登录 后发表评论