Commit 78c9560e authored by luojun's avatar luojun

Initial commit

parents
Pipeline #6320 canceled with stages
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="accountSettings">
<option name="activeRegion" value="us-east-1" />
<option name="recentlyUsedRegions">
<list>
<option value="us-east-1" />
</list>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="qy-zhjt" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="qy-ydp" target="1.8" />
</bytecodeTargetLevel>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="qy-ydp" options="-parameters" />
<module name="qy-zhjt" options="-parameters" />
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="justh5" />
<option name="name" value="Public Repositories" />
<option name="url" value="http://120.24.176.53:8081/repository/maven-public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="http://120.24.176.53:8081/repository/maven-public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>qy-zhjt</artifactId>
<name>静态交通大数据平台</name>
<properties>
<jjwt.version>0.11.1</jjwt.version>
<!-- oshi监控需要指定jna版本, 问题详见 https://github.com/oshi/oshi/issues/1040 -->
<jna.version>5.5.0</jna.version>
<druid.version>1.2.14</druid.version>
<mybatis-plus.version>3.3.0</mybatis-plus.version>
</properties>
<dependencies>
<!-- tools 模块包含了 common 和 logging 模块 -->
<dependency>
<groupId>base.admin</groupId>
<artifactId>base-common</artifactId>
<version>2.5</version>
<exclusions>
<exclusion>
<groupId>base.admin</groupId>
<artifactId>base-tools</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>base.admin</groupId>
<artifactId>base-logging</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.9</version>
</dependency>
<!-- Spring boot security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- linux的管理 -->
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>build210</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
</dependencies>
<distributionManagement>
<snapshotRepository>
<id>justh5-snapshots</id>
<url>http://120.24.176.53:8081/repository/justh5-snapshots/</url>
</snapshotRepository>
<repository>
<id>justh5-releases</id>
<url>http://120.24.176.53:8081/repository/justh5-releases/</url>
</repository>
</distributionManagement>
<!-- 打包 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 跳过单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
<!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</project>
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin;
import io.swagger.annotations.Api;
import admin.annotation.rest.AnonymousGetMapping;
import admin.utils.SpringContextHolder;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Api(hidden = true)
@SpringBootApplication(scanBasePackages = {"admin"},exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
@EnableScheduling
public class ZhjtRun {
public static void main(String[] args) {
SpringApplication.run(ZhjtRun.class, args);
}
@Bean
public SpringContextHolder springContextHolder() {
return new SpringContextHolder();
}
@Bean
public ServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory fa = new TomcatServletWebServerFactory();
fa.addConnectorCustomizers(connector -> connector.setProperty("relaxedQueryChars", "[]{}"));
return fa;
}
@AnonymousGetMapping("/")
public String index() {
return "Backend service started ";
}
}
package admin.annotation;
import java.lang.annotation.*;
/**
* @ClassName CurDataSource
* @Description 切换数据源注解
* @Author lzl
**/
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurDataSource {
/**
* 数据源名称,默认 ydp
*
* @return
*/
String value() default "master";
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>
* 用于判断是否过滤数据权限
* 1、如果没有用到 @OneToOne 这种关联关系,只需要填写 fieldName [参考:DeptQueryCriteria.class]
* 2、如果用到了 @OneToOne ,fieldName 和 joinName 都需要填写,拿UserQueryCriteria.class举例:
* 应该是 @DataPermission(joinName = "dept", fieldName = "id")
* </p>
* @website https://el-admin.vip
* @date 2020-05-07
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
/**
* Entity 中的字段名称
*/
String fieldName() default "";
/**
* Entity 中与部门关联的字段名称
*/
String joinName() default "";
/**
* 忽略场景配置
* 与 @Scene 配合使用
*/
String[] ignoreScene() default {};
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Zheng Jie
* @date 2019-6-4 13:52:30
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
// Dong ZhaoYang 2017/8/7 基本对象的属性名
String propName() default "";
// Dong ZhaoYang 2017/8/7 查询方式
Type type() default Type.EQUAL;
/**
* 连接查询的属性名,如User类中的dept
*/
String joinName() default "";
/**
* 默认左连接
*/
Join join() default Join.LEFT;
/**
* 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username")
*/
String blurry() default "";
enum Type {
// jie 2019/6/4 相等
EQUAL
// Dong ZhaoYang 2017/8/7 大于等于
, GREATER_THAN
// Dong ZhaoYang 2017/8/7 小于等于
, LESS_THAN
// Dong ZhaoYang 2017/8/7 中模糊查询
, INNER_LIKE
// Dong ZhaoYang 2017/8/7 左模糊查询
, LEFT_LIKE
// Dong ZhaoYang 2017/8/7 右模糊查询
, RIGHT_LIKE
// Dong ZhaoYang 2017/8/7 小于
, LESS_THAN_NQ
// jie 2019/6/4 包含
, IN
// 不包含
, NOT_IN
// 不等于
,NOT_EQUAL
// between
,BETWEEN
// 不为空
,NOT_NULL
// 为空
,IS_NULL
}
/**
* 适用于简单连接查询,复杂的请自定义该注解,或者使用sql查询
*/
enum Join {
/** jie 2019-6-4 13:18:30 */
LEFT, RIGHT, INNER
}
}
package admin.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 场景值注解 用于标识参数字段 是场景值字段
* @author fanglei
* @date 2021/09/06
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scene {
}
package admin.asept;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @Description 动态数据源
* @Author lzl
* @Date 2021-04-22
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
* 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
*/
private static final ThreadLocal<String> dataSourceName = new ThreadLocal<String>();
/**
* 支持以包名的粒度选择数据源
*/
private static final Map<String, String> packageDataSource = new HashMap<>();
public DynamicDataSource(DataSource firstDataSource, Map<Object, Object> targetDataSources) {
setDefaultTargetDataSource(firstDataSource);
setTargetDataSources(targetDataSources);
afterPropertiesSet();
}
/**
* 获取与线程上下文绑定的数据源名称(存储在ThreadLocal中)
*
* @return 返回数据源名称
*/
@Override
protected Object determineCurrentLookupKey() {
String dsName = dataSourceName.get();
dataSourceName.remove();
return dsName;
}
public static void setDataSourceName(String dataSource) {
dataSourceName.set(dataSource);
}
public static void usePackageDatasourceKey(String pkName) {
dataSourceName.set(packageDataSource.get(pkName));
}
public Map<String, String> getPackageDatasource() {
return packageDataSource;
}
public void setPackageDatasource(Map<String, String> packageDatasource) {
this.packageDataSource.putAll(packageDatasource);
}
}
package admin.asept;
import admin.annotation.CurDataSource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* 动态数据源切面类
* 被切中的,则先判断方法上是否有CurDataSource注解
* 然后判断方法所属类上是否有CurDataSource注解
* 其次判断是否配置了包级别的数据源
* <p>
* 优先级为方法、类、包
* 若同时配置则优先按方法上的
*
* @Author lzl
*/
@Slf4j
@Aspect
@Order(-1)//设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中)
@Component
public class DynamicDataSourceAspect {
// pointCut
@Pointcut("@annotation(admin.annotation.CurDataSource)")
public void choseDatasourceByAnnotation() {
}
@Pointcut("@within(admin.annotation.CurDataSource)")
public void choseDatasourceByClass() {
}
@Pointcut("execution(* admin.modules.*.service.impl..*(..))")
public void choseDatasourceByPackage() {
}
@Around("choseDatasourceByAnnotation() || choseDatasourceByClass() || choseDatasourceByPackage()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// log.info("进入AOP环绕通知");
Signature signature = joinPoint.getSignature();
String datasourceName = getDatasourceKey(signature);
if (!Objects.isNull(datasourceName)) {
DynamicDataSource.setDataSourceName(datasourceName);
}
return joinPoint.proceed();
}
/**
* 获取数据源
*
* @param signature
* @return
*/
private String getDatasourceKey(Signature signature) {
if (signature == null) {
return null;
} else {
if (signature instanceof MethodSignature) {
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
//@CurDataSource 注解是否在方法上
if (method.isAnnotationPresent(CurDataSource.class)) {
return this.dsSettingInMethod(method);
}
Class<?> declaringClass = method.getDeclaringClass();
//@CurDataSource 注解是否在类上
if (declaringClass.isAnnotationPresent(CurDataSource.class)) {
return this.dsSettingInConstructor(declaringClass);
}
Package aPackage = declaringClass.getPackage();
this.dsSettingInPackage(aPackage);
}
return null;
}
}
/**
* 获取类上 使用注解对应的数据源
*
* @param declaringClass
* @return
*/
private String dsSettingInConstructor(Class<?> declaringClass) {
CurDataSource dataSource = declaringClass.getAnnotation(CurDataSource.class);
return dataSource.value();
}
/**
* 获取方法上 使用注解对应的数据源
*
* @param method
* @return
*/
private String dsSettingInMethod(Method method) {
CurDataSource dataSource = method.getAnnotation(CurDataSource.class);
return dataSource.value();
}
/**
* 获取包对应的数据源
*
* @param pkg
*/
private void dsSettingInPackage(Package pkg) {
DynamicDataSource.usePackageDatasourceKey(pkg.getName());
}
}
/*
* Copyright (c) 2016, FPX and/or its affiliates. All rights reserved.
* Use, Copy is subject to authorized license.
*/
package admin.base;
import admin.utils.ApiResponseCode;
import lombok.Data;
@Data
public class ApiResponse<T> {
protected String code;
protected String message;
protected T data;
private String traceId;
public ApiResponse() {
}
public static <T> ApiResponse<T> buildSuccess() {
ApiResponse<T> response = new ApiResponse();
response.code = ApiResponseCode.SUCCESS.getCode();
response.message = ApiResponseCode.SUCCESS.getMessage();
return response;
}
public static <T> ApiResponse<T> buildSuccess(T data) {
ApiResponse<T> response = new ApiResponse();
response.data = data;
response.code = ApiResponseCode.SUCCESS.getCode();
response.message = ApiResponseCode.SUCCESS.getMessage();
return response;
}
public static <T> ApiResponse<T> buildFailure(ApiResponseCode responseCode) {
ApiResponse<T> response = new ApiResponse();
response.code = responseCode.getCode();
response.message = responseCode.getMessage();
return response;
}
public static <T> ApiResponse<T> buildFailure(String errorCode, String errorMsg) {
ApiResponse<T> response = new ApiResponse();
response.code = errorCode;
response.message = errorMsg;
return response;
}
public static boolean isSuccess(ApiResponse<Object> apiResponse){
return apiResponse.code.equals("0");
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
public String getCode() {
return this.code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return this.message;
}
public ApiResponse<T> setMessage(String message) {
this.message = message;
return this;
}
public T getData() {
return this.data;
}
public void setData(T data) {
this.data = data;
}
public String toString() {
return "data.ApiResponse{traceId='" + this.traceId + '\'' + ", code='" + this.code + '\'' + ", message='" + this.message + '\'' + ", data=" + this.data + '}';
}
}
package admin.base;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @date 2019年10月24日20:48:53
*/
@Data
public class BaseDTO implements Serializable {
private String createBy;
private String updatedBy;
private Date createTime;
private Date updateTime;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.base;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Date;
/**
* 抽象实体类 :带有公共字段,根据需求自行添加
* @Date 2019年10月24日20:46:32
*/
@Data
public abstract class BaseEntity<T extends Model<?>> extends CommonModel<T> implements Serializable {
@CreatedBy
// @Column(name = "create_by", updatable = false)
@ApiModelProperty(value = "创建人", hidden = true)
@TableField(value = "create_by")
private String createBy;
@LastModifiedBy
// @Column(name = "update_by")
@TableField(value = "update_by")
@ApiModelProperty(value = "更新人", hidden = true)
private String updatedBy;
@TableField(value = "create_time", fill = FieldFill.INSERT)
// @Column(name = "create_time", updatable = false)
@ApiModelProperty(value = "创建时间", hidden = true)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
// @Column(name = "update_time")
@ApiModelProperty(value = "更新时间", hidden = true)
private Date updateTime;
/* 分组校验 */
public @interface Create {}
/* 分组校验 */
public @interface Update {}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
Field[] fields = this.getClass().getDeclaredFields();
try {
for (Field f : fields) {
f.setAccessible(true);
builder.append(f.getName(), f.get(this)).append("\n");
}
} catch (Exception e) {
builder.append("toString builder encounter an error");
}
return builder.toString();
}
}
package admin.base;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 公共抽象Mapper接口类
* @author fanglei
* @date 2021/07/28
*/
@Mapper
public interface CommonMapper<E> extends MPJBaseMapper<E> {
default QueryChainWrapper<E> query() {
return ChainWrappers.queryChain(this);
}
default LambdaQueryChainWrapper<E> lambdaQuery() {
return ChainWrappers.lambdaQueryChain(this);
}
default UpdateChainWrapper<E> update() {
return ChainWrappers.updateChain(this);
}
default LambdaUpdateChainWrapper<E> lambdaUpdate() {
return ChainWrappers.lambdaUpdateChain(this);
}
}
package admin.base;
import com.baomidou.mybatisplus.extension.activerecord.Model;
/**
* 抽象实体类:无公共字段
*
* @author fanglei
* @date 2021/07/28 15:26
**/
public abstract class CommonModel<T extends Model<?>> extends Model<T> {
}
package admin.base;
import com.github.yulichang.base.MPJBaseService;
/**
* 公共抽象接口类
* @author fanglei
* @date 2021/07/28
*/
public interface CommonService<T> extends MPJBaseService<T> {
}
package admin.base;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* Created by jinjin on 2020-09-22.
*/
@Data
@Builder
@Accessors(chain = true)
public class PageInfo<T> implements Serializable{
@ApiModelProperty("总数量")
private long totalElements;
@ApiModelProperty("内容")
private List<T> content;
public PageInfo(long totalElements, List<T> content) {
this.totalElements = totalElements;
this.content = content;
}
public PageInfo() {
}
}
package admin.base;
import admin.annotation.DataPermission;
import admin.annotation.Query;
import admin.annotation.Scene;
import admin.utils.SecurityUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.*;
/**
* @author Zheng Jie
* @date 2019-6-4 14:59:48
*/
@Slf4j
public class QueryHelpMybatisPlus {
// TODO DataPermission
public static <R, Q> QueryWrapper<R> getPredicate(Q query) {
QueryWrapper<R> queryWrapper = new QueryWrapper<>();
if (query == null) {
return queryWrapper;
}
// 数据权限验证
DataPermission permission = query.getClass().getAnnotation(DataPermission.class);
if(permission != null){
// 获取数据权限
List<Long> dataScopes = SecurityUtils.getCurrentUserDataScope();
if(CollectionUtil.isNotEmpty(dataScopes)){
// 过滤场景
boolean ignoreScene = false;
if(ArrayUtil.isNotEmpty(permission.ignoreScene())) {
boolean exits = query.getClass().isAnnotationPresent(Scene.class);
if(exits) {
for(Field field : query.getClass().getDeclaredFields()) {
Scene scene = field.getAnnotation(Scene.class);
if (scene != null) {
Object sceneValue = ReflectionUtils.getField(field, query);
if(Objects.nonNull(sceneValue)) {
if(StringUtils.equalsAny(String.valueOf(sceneValue), permission.ignoreScene())) {
ignoreScene = true;
break;
}
}
}
}
} else {
throw new RuntimeException("需要用 @Scene 注解标识场景字段值, 根据字段对应的值判断是否忽略");
}
}
if(!ignoreScene) {
if(StringUtils.isNotBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
//Join join = root.join(permission.joinName(), JoinType.LEFT);
//list.add(getExpression(permission.fieldName(),join, root).in(dataScopes));
throw new RuntimeException("未实现");
} else if (StringUtils.isBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
//list.add(getExpression(permission.fieldName(),null, root).in(dataScopes));
queryWrapper.in(permission.fieldName(), dataScopes);
}
}
}
}
try {
List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
for (Field field : fields) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
Query q = field.getAnnotation(Query.class);
if (q != null) {
String propName = q.propName();
String blurry = q.blurry();
String attributeName = StrUtil.isBlank(propName) ? field.getName() : propName;
attributeName = humpToUnderline(attributeName);
Class<?> fieldType = field.getType();
Object val = field.get(query);
if (ObjectUtil.isNull(val) || "".equals(val)) {
continue;
}
// 模糊多字段
if (ObjectUtil.isNotEmpty(blurry)) {
String[] blurrys = blurry.split(",");
//queryWrapper.or();
queryWrapper.and(wrapper -> {
for (String blurry1 : blurrys) {
String column = humpToUnderline(blurry1);
//if(i!=0){
wrapper.or();
//}
wrapper.like(column, val.toString());
}
});
continue;
}
String finalAttributeName = attributeName;
switch (q.type()) {
case EQUAL:
//queryWrapper.and(wrapper -> wrapper.eq(finalAttributeName, val));
queryWrapper.eq(attributeName, val);
break;
case GREATER_THAN:
queryWrapper.ge(finalAttributeName, val);
break;
case LESS_THAN:
queryWrapper.le(finalAttributeName, val);
break;
case LESS_THAN_NQ:
queryWrapper.lt(finalAttributeName, val);
break;
case INNER_LIKE:
queryWrapper.like(finalAttributeName, val);
break;
case LEFT_LIKE:
queryWrapper.likeLeft(finalAttributeName, val);
break;
case RIGHT_LIKE:
queryWrapper.likeRight(finalAttributeName, val);
break;
case IN:
if (CollUtil.isNotEmpty((Collection<Long>) val)) {
queryWrapper.in(finalAttributeName, (Collection<Long>) val);
}
break;
case NOT_IN:
if (CollUtil.isNotEmpty((Collection<Long>) val)) {
queryWrapper.notIn(finalAttributeName, (Collection<Long>) val);
}
break;
case NOT_EQUAL:
queryWrapper.ne(finalAttributeName, val);
break;
case NOT_NULL:
queryWrapper.isNotNull((Boolean) val,finalAttributeName);
break;
case IS_NULL:
queryWrapper.isNull((Boolean) val,finalAttributeName);
break;
case BETWEEN:
List<Object> between = new ArrayList<>((List<Object>) val);
queryWrapper.between(finalAttributeName, between.get(0), between.get(1));
break;
default:
break;
}
}
field.setAccessible(accessible);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return queryWrapper;
}
public static List<Field> getAllFields(Class clazz, List<Field> fields) {
if (clazz != null) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
getAllFields(clazz.getSuperclass(), fields);
}
return fields;
}
/***
* 驼峰命名转为下划线命名
*
* @param para
* 驼峰命名的字符串
*/
public static String humpToUnderline(String para) {
return StrUtil.toUnderlineCase(para);
}
// public static void main(String[] args) {
// QueryWrapper<Paging> query = new QueryWrapper<Paging>();
// //query.or();
// query.or(wrapper -> wrapper.eq("store_id", 1).or().eq("store_id", 2));
// //query.like("a",1);
// //query.or();
// //query.like("b",2);
// //query.and(wrapper->wrapper.eq("c",1));
// query.eq("1", 1);
//
// System.out.println(query.getSqlSegment());
// }
}
package admin.base.impl;
import admin.base.CommonService;
import com.github.yulichang.base.MPJBaseMapper;
import com.github.yulichang.base.MPJBaseServiceImpl;
/**
* 公共抽象service实现类
* @author fanglei
* @date 2021/07/28
*/
public abstract class CommonServiceImpl<M extends MPJBaseMapper<T>, T> extends MPJBaseServiceImpl<M, T> implements CommonService<T> {
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* WebMvcConfigurer
*
* @date 2018-11-30
*/
@Configuration
@EnableWebMvc
public class ConfigurerAdapter implements WebMvcConfigurer {
/** 文件配置 */
private final FileProperties properties;
public ConfigurerAdapter(FileProperties properties) {
this.properties = properties;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
FileProperties.ElPath path = properties.getPath();
String avatarUtl = "file:" + path.getAvatar().replace("\\","/");
String pathUtl = "file:" + path.getPath().replace("\\","/");
registry.addResourceHandler("/avatar/**").addResourceLocations(avatarUtl).setCachePeriod(0);
registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0);
registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
}
}
package admin.config;
import admin.asept.DynamicDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author lj
* @date 2024/11/19 13:24
*/
@Configuration
@EnableAutoConfiguration
public class DataSourceConfig{
@Bean(value = "master")
// @Primary
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
public DataSource createDataSourceYdp(){
System.out.println("ydp数据源");
return DruidDataSourceBuilder.create().build();
}
@Bean(value="slaver")
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slaver")
public DataSource createDataSourceQy(){
System.out.println("qy数据源");
return DruidDataSourceBuilder.create().build();
}
/**
* 动态数据源引入
*
* @param masterDataSource
* @return
*/
@Bean
@Primary
public DynamicDataSource dataSource(@Qualifier("master") DataSource ydpDataSource,
@Qualifier("slaver") DataSource qyDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>(3);
targetDataSources.put("master", ydpDataSource);
targetDataSources.put("slaver", qyDataSource);
//配置包级别的数据源
Map<String, String> packageDataSource = new HashMap<>();
//默认包使用的数据源
packageDataSource.put("admin.modules","master");
DynamicDataSource dynamicDataSource = new DynamicDataSource(ydpDataSource, targetDataSources);
dynamicDataSource.setPackageDatasource(packageDataSource);
dynamicDataSource.afterPropertiesSet();
return dynamicDataSource;
}
}
package admin.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置文件
* Created by jinjin on 2020-09-21.
*/
@Configuration
@MapperScan(basePackages ={"admin.**.mapper"})
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
// @Bean
// public GlobalConfig globalConfig() {
// GlobalConfig globalConfig = new GlobalConfig();
// globalConfig.setMetaObjectHandler(new MybatisPlusFillHandler());
// return globalConfig;
// }
}
package admin.config;
import admin.utils.SecurityUtils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Date;
/**
* Created by jinjin on 2020-09-21.
*/
@Configuration
public class MybatisPlusFillHandler implements MetaObjectHandler{
@Override
public void insertFill(MetaObject metaObject) {
Date currentTime = new Date();
if (metaObject.hasSetter("createTime")) {
Class<?> clazz = metaObject.getSetterType("createTime");
if(Long.class.getName().equals(clazz.getName())) {
setFieldValByName("createTime", System.currentTimeMillis(), metaObject);
} else if (Timestamp.class.getName().equals(clazz.getName())) {
setFieldValByName("createTime", Timestamp.valueOf(LocalDateTime.now()), metaObject);
} else {
setFieldValByName("createTime", currentTime, metaObject);
}
}
if (metaObject.hasSetter("createBy")) {
setFieldValByName("createBy", getUsername(), metaObject);
}
if (metaObject.hasSetter("updateTime")) {
Class<?> clazz = metaObject.getSetterType("updateTime");
if(Long.class.getName().equals(clazz.getName())) {
setFieldValByName("updateTime", System.currentTimeMillis(), metaObject);
} else if (Timestamp.class.getName().equals(clazz.getName())) {
setFieldValByName("updateTime", Timestamp.valueOf(LocalDateTime.now()), metaObject);
} else {
setFieldValByName("updateTime", currentTime, metaObject);
}
}
if (metaObject.hasSetter("updateBy")) {
setFieldValByName("updateBy", getUsername(), metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
Date currentTime = new Date();
if (metaObject.hasSetter("updateTime")) {
Class<?> clazz = metaObject.getSetterType("updateTime");
if(Long.class.getName().equals(clazz.getName())) {
setFieldValByName("updateTime", System.currentTimeMillis(), metaObject);
} else if (Timestamp.class.getName().equals(clazz.getName())) {
setFieldValByName("createTime", Timestamp.valueOf(LocalDateTime.now()), metaObject);
}else if (Timestamp.class.getName().equals(clazz.getName())) {
setFieldValByName("updateTime", Timestamp.valueOf(LocalDateTime.now()), metaObject);
} else {
setFieldValByName("updateTime", currentTime, metaObject);
}
}
if (metaObject.hasSetter("updateBy")) {
setFieldValByName("updateBy", getUsername(), metaObject);
}
}
private String getUsername() {
try {
return SecurityUtils.getCurrentUsername();
} catch (Exception e) {
return "";
}
}
}
package admin.config;
public class RedisCacheConfigPath {
/**
* 手持app登录用户缓存
* */
public static final String loginUserCacheKey="qyydp:app:login:login_user_cache_%s";
/**
* 温度缓存
*/
public static final String weatherTextCacheKey="qyydp:nova:weather_cache";
/**
* 门户跳转token
*/
public static final String syncTokenGetKey="qyydp:system:sync:portal_token";
/**
* 门户跳转token
*/
public static final String syncTokenVerifyKey="qyydp:system:sync:verify_user_token_%s";
}
package admin.model.menu;
import lombok.Data;
@Data
public class ShowNavModel {
private Long id;
private Integer showNav;
private Integer navQuick;
private String title;
private String path;
private Integer isLink; //是否是外部链接
}
/*
* Copyright (c) 2016, FPX and/or its affiliates. All rights reserved.
* Use, Copy is subject to authorized license.
*/
package admin.model.response;
import admin.utils.enums.ApiResponseCode;
import lombok.Data;
@Data
public class ApiResponse<T> {
protected String code;
protected String message;
protected T data;
private String traceId;
public ApiResponse() {
}
public static <T> ApiResponse<T> buildSuccess() {
ApiResponse<T> response = new ApiResponse();
response.code = ApiResponseCode.SUCCESS.getCode();
response.message = ApiResponseCode.SUCCESS.getMessage();
return response;
}
public static <T> ApiResponse<T> buildSuccess(T data) {
ApiResponse<T> response = new ApiResponse();
response.data = data;
response.code = ApiResponseCode.SUCCESS.getCode();
response.message = ApiResponseCode.SUCCESS.getMessage();
return response;
}
public static <T> ApiResponse<T> buildFailure(ApiResponseCode responseCode) {
ApiResponse<T> response = new ApiResponse();
response.code = responseCode.getCode();
response.message = responseCode.getMessage();
return response;
}
public static <T> ApiResponse<T> buildFailure(String errorCode, String errorMsg) {
ApiResponse<T> response = new ApiResponse();
response.code = errorCode;
response.message = errorMsg;
return response;
}
public static boolean isSuccess(ApiResponse<Object> apiResponse){
return apiResponse.code.equals("0");
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
public String getCode() {
return this.code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return this.message;
}
public ApiResponse<T> setMessage(String message) {
this.message = message;
return this;
}
public T getData() {
return this.data;
}
public void setData(T data) {
this.data = data;
}
public String toString() {
return "data.ApiResponse{traceId='" + this.traceId + '\'' + ", code='" + this.code + '\'' + ", message='" + this.message + '\'' + ", data=" + this.data + '}';
}
}
package admin.model.syncToken;
import lombok.Data;
@Data
public class GetTokenRep {
private String access_token;
private Long code;
private Integer roleid;
private String token_type;
private Integer expires_in;
}
package admin.model.syncToken;
import lombok.Data;
@Data
public class VerifyRep {
private String msg;
private String code;
private DataRep data;
@Data
public static class DataRep{
private String redirectUrl;
private String userName;
}
}
package admin.model.syncToken;
import lombok.Data;
@Data
public class VerifyReq {
private String unifiedUserToken;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.domain;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Timestamp;
/**
* @author wl
* @website https://el-admin.vip
* @description /
* @date 2020-10-06
**/
//@Entity
@Data
//@Table(name="parking_in_info")
@TableName("parking_in_info")
public class ParkingInInfo implements Serializable {
@Id
@TableId(value = "parking_in_id", type = IdType.AUTO)
@ApiModelProperty(value = "parkingInId")
private Long parkingInId;
@TableField(value = "secret_key")
@ApiModelProperty(value = "订单号")
private String secretKey;
@TableField(value = "parking_in_serial")
@ApiModelProperty(value = "车场编号")
private String parkingInSerial;
@TableField(value = "parking_in_no")
@ApiModelProperty(value = "车场id")
private String parkingInNo;
@TableField(value = "user_id")
@ApiModelProperty(value = "用户id")
private String userId;
@TableField(value = "user_account")
@ApiModelProperty(value = "账号")
private String userAccount;
@TableField(value = "user_name")
@ApiModelProperty(value = "用户名称")
private String userName;
@TableField(value = "user_password")
@ApiModelProperty(value = "密码")
private String userPassword;
@TableField(value = "charge_rule_desc")
@ApiModelProperty(value = "收费规则")
private String chargeRuleDesc;
@TableField(value = "station_no")
@ApiModelProperty(value = "终端号")
private String stationNo;
@TableField(value = "month_pay_amount")
@ApiModelProperty(value = "是否允许包月1:允许")
private BigDecimal monthPayAmount;
@TableField(value = "can_month_pay")
@ApiModelProperty(value = "是否允许包月1:允许")
private Integer canMonthPay;
@TableField(value = "parking_self")
@ApiModelProperty(value = "是否自营0:自营,1:非自营")
private Integer parkingSelf;
@TableField(value = "in_use")
@ApiModelProperty(value = "是否使用中:1:使用中")
private Integer inUse=1;
@TableField(value = "parking_type")
@ApiModelProperty(value = "车场类型:1立方车场,2海康车场")
private Integer parkingType;
@TableField(value = "total_parking_num")
@ApiModelProperty(value = "总车位数")
private Integer totalParkingNum;
@TableField(value = "use_parking_num")
@ApiModelProperty(value = "占用车位数")
private Integer useParkingNum;
@TableField(value = "channel_id")
@ApiModelProperty(value = "通道id")
private String channelId;
@TableField(value = "latitude")
@ApiModelProperty(value = "高德坐标")
private String latitude;
@TableField(value = "mac_id")
@ApiModelProperty(value = "商户id")
private String macId;
@TableField(value = "mac_key")
@ApiModelProperty(value = "商户key")
private String macKey;
@TableField(value = "longitude")
@ApiModelProperty(value = "高德坐标")
private String longitude;
@TableField(value = "screen_code")
@ApiModelProperty(value = "屏码")
private String screenCode;
@TableField(value = "screen_x")
@ApiModelProperty(value = "屏宽")
private Integer screenX;
@TableField(value = "screen_y")
@ApiModelProperty(value = "屏高")
private Integer screenY;
@TableField(value = "screen_font")
@ApiModelProperty(value = "字体")
private Integer screenFont;
@TableField(value = "create_time")
@ApiModelProperty(value = "createTime")
private Timestamp createTime;
@TableField(value = "tx__latitude")
@ApiModelProperty(value = "腾讯坐标")
private String txLatitude;
@TableField(value = "tx_longitude")
@ApiModelProperty(value = "腾讯坐标")
private String txLongitude;
@TableField(value = "parking_in_name")
@ApiModelProperty(value = "车场名称")
private String parkingInName;
@TableField(value = "parking_in_address")
@ApiModelProperty(value = "车场详细地址")
private String parkingInAddress;
@TableField(value = "parking_in_desc")
@ApiModelProperty(value = "车场详细描述")
private String parkingInDesc;
@TableField(value = "vr_path")
@ApiModelProperty(value = "车场VR地址")
private String vrPath;
@TableField(value = "parent_park")
@ApiModelProperty(value = "父车场标识")
private Integer parentPark;
@TableField(value = "child_key_val")
@ApiModelProperty(value = "子车场标识")
private String childKeyVal;
@TableField(value = "temp_fee_rule")
@ApiModelProperty(value = "父车场标识")
private Integer tempFeeRule;
@TableField(value = "road_id")
@ApiModelProperty(value = "所属路段id")
private Long roadId;
@TableField(value = "road_name")
@ApiModelProperty(value = "路段名称")
private String roadName;
@TableField(value = "parking_domain")
@ApiModelProperty(value = "域名")
private String parkingDomain;
@TableField(value = "park_out_qrcode_1")
@ApiModelProperty(value = "出场二维码地址")
private String parkOutQrcode1;
@TableField(value = "park_out_qrcode_2")
@ApiModelProperty(value = "出场二维码地址")
private String parkOutQrcode2;
@TableField(value = "park_out_qrcode_3")
@ApiModelProperty(value = "出场二维码地址")
private String parkOutQrcode3;
@TableField(value = "park_out_qrcode_4")
@ApiModelProperty(value = "出场二维码地址")
private String parkOutQrcode4;
@TableField(value = "park_temp_in_qrcode_1")
@ApiModelProperty(value = "入口1的二维码")
private String parkTempInQrcode1;
@TableField(value = "park_temp_in_qrcode_2")
@ApiModelProperty(value = "入口1的二维码")
private String parkTempInQrcode2;
@TableField(value = "park_temp_in_qrcode_3")
@ApiModelProperty(value = "入口1的二维码")
private String parkTempInQrcode3;
@TableField(value = "park_temp_in_qrcode_4")
@ApiModelProperty(value = "入口4的二维码")
private String parkTempInQrcode4;
@TableField(value = "park_temp_out_qrcode_1")
@ApiModelProperty(value = "出口1的二维码")
private String parkTempOutQrcode1;
@TableField(value = "park_temp_out_qrcode_2")
@ApiModelProperty(value = "出口2的二维码")
private String parkTempOutQrcode2;
@TableField(value = "park_temp_out_qrcode_3")
@ApiModelProperty(value = "出口3的二维码")
private String parkTempOutQrcode3;
@TableField(value = "park_temp_out_qrcode_4")
@ApiModelProperty(value = "出口4的二维码")
private String parkTempOutQrcode4;
@TableField(value = "park_fee")
@ApiModelProperty(value = "出口4的二维码")
private String parkFee;
@TableField(value = "plan_img")
@ApiModelProperty(value = "出口4的二维码")
private String planImg;
@TableField(value = "pos_json")
@ApiModelProperty(value = "位置边界")
private String posJson;
@TableField(value = "channel_coordinates")
@ApiModelProperty(value = "位置边界")
private String channelCoordinates;
@TableField(value = "is_referrer")
@ApiModelProperty(value = "是否推荐 0 不推荐 1 推荐")
private Integer isReferrer;
@TableField(value = "yun_zong_parking_id")
@ApiModelProperty(value = "云纵支付车场id")
private String yunZongParkingId;
@TableField(value = "park_etc")
@ApiModelProperty(value = "车场etc是否开通 1 开通")
private Integer parkEtc;
@TableField(value = "pay_type")
@ApiModelProperty(value = "聚合支付类型 1 云纵 2 中科支付")
private Integer payType;
@TableField(value = "zk_mch_no")
@ApiModelProperty(value = "中科商户号")
private String zkMchNo;
@TableField(value = "zk_app_id")
@ApiModelProperty(value = "中科应用id")
private String zkAppId;
@TableField(value = "zk_secret_key")
@ApiModelProperty(value = "商户密钥key")
private String zkSecretKey;
@TableField(value = "zk_division_mode")
@ApiModelProperty(value = "否 分账模式: 0-该笔订单不允许分账[默认], 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)")
private Integer zkDivisionMode;
@TableField(value = "union_pay_appid")
@ApiModelProperty(value = "银联支付appid")
private String unionPayAppid;
@TableField(value = "show_screen")
@ApiModelProperty(value = "是否在大屏展示 0:不展示 1:展示")
private String showScreen;
@TableField(value = "is_charging")
@ApiModelProperty(value = "是否带有充电桩 0:仅停车无充电 1:停车充电均有 2:仅充电")
private Integer isCharging;
@TableField(value = "is_cst")
@ApiModelProperty(value = "0-社会停车场 1-城顺通车场")
private Integer isCst;
@TableField(value = "charging_num")
@ApiModelProperty(value = "充电桩数量")
private Integer chargingNum;
@TableField(value = "operate_company_id")
@ApiModelProperty(value = "运营公司id")
private Long operateCompanyId;
@TableField(value = "recovery_status")
@ApiModelProperty(value = "追缴状态 追缴状态:1-开启 0-关闭")
private Integer recoveryStatus;
public void copy(ParkingInInfo source) {
BeanUtil.copyProperties(source, this, CopyOptions.create().setIgnoreNullValue(true));
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.domain;
import admin.utils.DateUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* @author wl
* @website https://el-admin.vip
* @description /
* @date 2020-07-12
**/
//@Entity
@Data
//@Table(name="parking_info")
@TableName("parking_info")
public class ParkingInfo implements Serializable {
@Id
// @GeneratedValue(strategy = GenerationType.IDENTITY)
// @Column(name = "parking_id")
@TableId(value = "parking_id", type = IdType.AUTO)
@ApiModelProperty(value = "车位id")
private Long parkingId;
// @Column(name = "parking_serial",nullable = false)
@TableField(value = "parking_serial")
// @NotBlank
@ApiModelProperty(value = "车位编号")
private String parkingSerial;
@TableField(value = "parking_name")
@ApiModelProperty(value = "车位名称")
private String parkingName;
// @Column(name = "road_id")
@TableField(value = "road_id")
@ApiModelProperty(value = "路段id")
private Long roadId;
@TableField(value = "type")
@ApiModelProperty(value = "设备类型 1视频桩 2地磁 3巡检车 4 高位")
private Integer type;
// @Column(name = "status")
@TableField(value = "status")
@ApiModelProperty(value = "车位状态 1:占用 2:空闲")
private Integer status = 2;
// @Column(name = "job_id")
@TableField(value = "job_id",updateStrategy = FieldStrategy.IGNORED)
@ApiModelProperty(value = "岗位id")
private Long jobId;
// @Column(name = "device_id")
@TableField(value = "device_id")
@ApiModelProperty(value = "设备id")
private Long deviceId;
// @Column(name = "job_name")
@TableField(value = "job_name",updateStrategy = FieldStrategy.IGNORED)
@ApiModelProperty(value = "岗位名称")
private String jobName;
// @Column(name = "road_name")
@TableField(value = "road_name")
@ApiModelProperty(value = "路段名称")
private String roadName;
// @Column(name = "device_serial")
@TableField(value = "device_serial")
@ApiModelProperty(value = "设备编号")
private String deviceSerial;
// @Column(name = "latitude")
@TableField(value = "latitude")
@ApiModelProperty(value = "经纬度")
private String latitude;
// @Column(name = "longitude")
@TableField(value = "longitude")
@ApiModelProperty(value = "经纬度")
private String longitude;
// @Column(name = "create_time")
@TableField(value = "create_time")
@ApiModelProperty(value = "createTime")
private Timestamp createTime = DateUtil.getNowTimestamp();
@TableField(exist = false)
@ApiModelProperty(value = "车场泊位前缀")
private String berthPrefix;
@TableField(exist = false)
@ApiModelProperty(value = "开始数字")
private int startNum;
@TableField(exist = false)
@ApiModelProperty(value = "结束数字")
private int endNum;
public void copy(ParkingInfo source) {
BeanUtil.copyProperties(source, this, CopyOptions.create().setIgnoreNullValue(true));
}
public static boolean checkNum(ParkingInfo source){
if(ObjectUtil.isEmpty(source.getStartNum())){
source.setStartNum(0);
}
if(ObjectUtil.isEmpty(source.getEndNum())){
source.setEndNum(0);
}
if(source.startNum<0||source.endNum<0||source.startNum> source.endNum){
return false;
}
return true;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.domain;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.annotation.Id;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* @author wl
* @website https://el-admin.vip
* @description /
* @date 2020-07-12
**/
//@Entity
@Data
//@Table(name="parking_road_info")
@TableName("parking_road_info")
public class ParkingRoadInfo implements Serializable {
@Id
// @GeneratedValue(strategy = GenerationType.IDENTITY)
// @Column(name = "road_id")
@TableId(value = "road_id", type = IdType.AUTO)
@ApiModelProperty(value = "路段id")
private Long roadId;
// @Column(name = "road_name",nullable = false)
@TableField(value = "road_name")
@NotBlank
@ApiModelProperty(value = "路段名称")
private String roadName;
@ApiModelProperty(value = "费用规则id")
@TableField(value = "fee_rule_id")
private Long feeRuleId;
@TableField(value = "road_type")
@ApiModelProperty(value = "路段类型 0:人工,1:地磁,2:高位,3:视频桩")
private Integer roadType;
// @Column(name = "road_serial",nullable = false)
@TableField(value = "road_serial")
@NotBlank
@ApiModelProperty(value = "路段编号")
private String roadSerial;
// @Column(name = "province_id")
@TableField(value = "province_id")
@ApiModelProperty(value = "省id")
private Integer provinceId;
// @Column(name = "province_name")
@TableField(value = "province_name")
@ApiModelProperty(value = "provinceName")
private String provinceName;
// @Column(name = "city_id")
@TableField(value = "city_id")
@ApiModelProperty(value = "cityId")
private Integer cityId;
// @Column(name = "city_name")
@TableField(value = "city_name")
@ApiModelProperty(value = "cityName")
private String cityName;
// @Column(name = "area_id")
@TableField(value = "area_id")
@ApiModelProperty(value = "areaId")
private Integer areaId;
// @Column(name = "area_name")
@TableField(value = "area_name")
@ApiModelProperty(value = "areaName")
private String areaName;
// @Column(name = "address")
@TableField(value = "address")
@ApiModelProperty(value = "address")
private String address;
// @Column(name = "latitude")
@TableField(value = "latitude")
@ApiModelProperty(value = "latitude")
private String latitude;
// @Column(name = "longitude")
@TableField(value = "longitude")
@ApiModelProperty(value = "longitude")
private String longitude;
// @Column(name = "tx_latitude")
@TableField(value = "tx_latitude")
@ApiModelProperty(value = "tx_latitude")
private String txLatitude;
// @Column(name = "tx_longitude")
@TableField(value = "tx_longitude")
@ApiModelProperty(value = "tx_longitude")
private String txLongitude;
@TableField(value = "status")
@ApiModelProperty(value = "路段状态 1:启用,2:禁用")
private Integer status=1;
@TableField(value = "total_parkingnum")
@ApiModelProperty(value = "路段总车位数")
private Integer totalParkingnum;
@TableField(value = "create_time")
@ApiModelProperty(value = "createTime")
private Timestamp createTime;
@TableField(value = "park_fee")
@ApiModelProperty(value = "出口4的二维码")
private String parkFee;
@TableField(value = "plan_img")
@ApiModelProperty(value = "出口4的二维码")
private String planImg;
@TableField(value = "vr_path")
@ApiModelProperty(value = "VR地址")
private String vrPath;
@TableField(value = "pay_type")
@ApiModelProperty(value = "聚合支付类型 1 云纵 2 中科支付")
private Integer payType;
@TableField(value = "zk_mch_no")
@ApiModelProperty(value = "中科商户号")
private String zkMchNo;
@TableField(value = "zk_app_id")
@ApiModelProperty(value = "中科应用id")
private String zkAppId;
@TableField(value = "zk_secret_key")
@ApiModelProperty(value = "商户密钥key")
private String zkSecretKey;
@TableField(value = "zk_division_mode")
@ApiModelProperty(value = "否 分账模式: 0-该笔订单不允许分账[默认], 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)")
private Integer zkDivisionMode;
@TableField(value = "show_screen")
@ApiModelProperty(value = "是否在大屏展示 0:不展示 1:展示")
private String showScreen;
@TableField(value = "is_charging")
@ApiModelProperty(value = "是否带有充电桩 0:仅停车无充电 1:停车充电均有 2:仅充电")
private Integer isCharging;
@TableField(value = "charging_num")
@ApiModelProperty(value = "充电桩数量")
private Integer chargingNum;
@TableField(value = "pos_json")
@ApiModelProperty(value = "位置边界")
private String posJson;
public void copy(ParkingRoadInfo source) {
BeanUtil.copyProperties(source, this, CopyOptions.create().setIgnoreNullValue(true));
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.service;
import admin.base.CommonService;
import admin.base.PageInfo;
import admin.modules.parking.domain.ParkingInInfo;
import admin.modules.parking.service.dto.ParkingInInfoDto;
import admin.modules.parking.service.dto.ParkingInInfoQueryCriteria;
import org.springframework.data.domain.Pageable;
import java.util.List;
/**
* @website https://el-admin.vip
* @description 服务接口
* @author wl
* @date 2020-10-06
**/
public interface ParkingInInfoService extends CommonService<ParkingInInfo> {
/**
* 查询数据分页
* @param criteria 条件
* @param pageable 分页参数
* @return PageInfo<ParkingInInfoDto>
*/
PageInfo<ParkingInInfoDto> queryAll(ParkingInInfoQueryCriteria criteria, Pageable pageable);
/**
* 查询所有数据不分页
* @param criteria 条件参数
* @return List<ParkingInInfoDto>
*/
List<ParkingInInfoDto> queryAll(ParkingInInfoQueryCriteria criteria);
/**
* 根据ID查询
* @param parkingInId ID
* @return ParkingInInfoDto
*/
ParkingInInfoDto findById(Long parkingInId);
/**
* 查询所有车场
*/
List<ParkingInInfoDto> findAll();
/**
* 创建
* @param resources /
* @return ParkingInInfoDto
*/
ParkingInInfoDto create(ParkingInInfo resources);
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.service;
import admin.base.CommonService;
import admin.base.PageInfo;
import admin.modules.parking.domain.ParkingRoadInfo;
import admin.modules.parking.service.dto.ParkingRoadInfoDto;
import admin.modules.parking.service.dto.ParkingRoadInfoQueryCriteria;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* @website https://el-admin.vip
* @description 服务接口
* @author wl
* @date 2020-07-12
**/
public interface ParkingRoadInfoService extends CommonService<ParkingRoadInfo> {
/**
* 查询数据分页
* @param criteria 条件
* @param pageable 分页参数
* @return PageInfo<ParkingRoadInfoDto>
*/
PageInfo<ParkingRoadInfoDto> queryAll(ParkingRoadInfoQueryCriteria criteria, Pageable pageable);
/**
* 查询所有数据不分页
* @param criteria 条件参数
* @return List<ParkingRoadInfoDto>
*/
List<ParkingRoadInfoDto> queryAll(ParkingRoadInfoQueryCriteria criteria);
/**
* 根据ID查询
* @param roadId ID
* @return ParkingRoadInfoDto
*/
ParkingRoadInfoDto findById(Long roadId);
/**
* 根据路段区域路段编号查询
* @param areaId
* @param roadSerial
* @return
*/
ParkingRoadInfo findByAreaIdAndRoadSerial(String areaId, String roadSerial);
/**
* 创建
* @param resources /
* @return ParkingRoadInfoDto
*/
ParkingRoadInfoDto create(ParkingRoadInfo resources);
/**
* 编辑
* @param resources /
*/
void update(ParkingRoadInfo resources);
/**
* 多选删除
* @param ids /
*/
void deleteAll(Long[] ids);
/**
* 导出数据
* @param all 待导出的数据
* @param response /
* @throws IOException /
*/
void download(List<ParkingRoadInfoDto> all, HttpServletResponse response) throws IOException;
/**
* 查找车位数量
*/
Integer parkingInfoCount(Long roadId,Integer status);
/**
* 查询路段所有的信息
* @return
*/
List<ParkingRoadInfoDto> getRoadInfo();
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.service.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* @website https://el-admin.vip
* @description /
* @author wl
* @date 2020-10-06
**/
@Data
public class ParkingInInfoDto implements Serializable {
private Long parkingInId;
private String parkingInNo;
/** 车场编号 */
private String parkingInSerial;
private Integer parkingType;
/** 总车位数 */
private Integer totalParkingNum;
private String secretKey;
private Integer parkingSelf;
private String channelId;
/** 占用车位数 */
private Integer useParkingNum;
/**是否使用中 1:使用中*/
private Integer inUse=1;
/** 高德坐标 */
private String latitude;
/**临时收费规则id*/
private Integer tempFeeRule;
private String vrPath;
/**父车场标识*/
private Integer parentPark;
/**子车场标识*/
private String childKeyVal;
/** 高德坐标 */
private String longitude;
private String screenCode;
private Integer screenX;
private Integer screenY;
private Integer screenFont;
private Timestamp createTime;
/** 腾讯坐标 */
private String txLatitude;
/** 腾讯坐标 */
private String txLongitude;
/** 车场名称 */
private String parkingInName;
/** 车场详细地址 */
private String parkingInAddress;
/** 车场详细描述 */
private String parkingInDesc;
/** 所属路段id */
private Long roadId;
private String macId;
private String macKey;
/** 路段名称 */
private String roadName;
private String userId;
private String userAccount;
private String userName;
private String userPassword;
private String chargeRuleDesc;
private String stationNo;
private String parkingDomain;
private String parkOutQrcode1;
private String parkOutQrcode2;
private String parkOutQrcode3;
private String parkOutQrcode4;
private String parkTempInQrcode1;
private String parkTempInQrcode2;
private String parkTempInQrcode3;
private String parkTempInQrcode4;
private String parkTempOutQrcode1;
private String parkTempOutQrcode2;
private String parkTempOutQrcode3;
private String parkTempOutQrcode4;
private String parkFee;
private String planImg;
private String posJson;
private String channelCoordinates;
private Integer isReferrer;
private String yunZongParkingId;
private Integer parkEtc;
@ApiModelProperty(value = "聚合支付类型 1 云纵 2 中科支付")
private Integer payType;
@ApiModelProperty(value = "中科商户号")
private String zkMchNo;
@ApiModelProperty(value = "中科应用id")
private String zkAppId;
@ApiModelProperty(value = "商户密钥key")
private String zkSecretKey;
@ApiModelProperty(value = "否 分账模式: 0-该笔订单不允许分账[默认], 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)")
private Integer zkDivisionMode;
private String unionPayAppid;
private String showScreen;
private Integer isCharging;
private Integer isCst;
private Integer chargingNum;
/**
* 追缴状态 追缴状态:1-开启 0-关闭
*/
private Integer recoveryStatus;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.service.dto;
import admin.annotation.Query;
import lombok.Data;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
/**
* @website https://el-admin.vip
* @author wl
* @date 2020-10-06
**/
@Data
public class ParkingInInfoQueryCriteria{
@Query
private Integer canReservation;
@Query(type = Query.Type.NOT_EQUAL,propName = "parkingSelf")
private Integer parkingSelfNotEQ;
@Query
private Long parkingInId;
@Query(type = Query.Type.IN,propName = "parkingInId")
private Set<Long> inParkingInId;
/** 模糊 */
@Query(type = Query.Type.INNER_LIKE)
private String parkingInSerial;
@Query
private Integer parkingType;
@Query
private Integer inUse;
/** 模糊 */
@Query(type = Query.Type.INNER_LIKE)
private String parkingInName;
/** 模糊 */
@Query(type = Query.Type.INNER_LIKE)
private String parkingInAddress;
@Query
private Integer parkingSelf;
/** 模糊 */
@Query(type = Query.Type.INNER_LIKE)
private String parkingInDesc;
/** 精确 */
@Query
private Long roadId;
/** 模糊 */
@Query(type = Query.Type.INNER_LIKE)
private String roadName;
/** BETWEEN */
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
/** 精确 */
@Query
private Integer isReferrer;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.service.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* @website https://el-admin.vip
* @description /
* @author wl
* @date 2020-07-12
**/
@Data
public class ParkingRoadInfoDto implements Serializable {
/** 路段id */
private Long roadId;
/** 路段名称 */
private String roadName;
private Long feeRuleId;
/** 路段编号 */
private String roadSerial;
//路段类型 0:人工,1:地磁,2:高位,3:视频桩
private Integer roadType;
/** 省id */
private Integer provinceId;
private String provinceName;
private Integer cityId;
private String cityName;
private Integer areaId;
private String areaName;
private String address;
private String latitude;
private String longitude;
private String txLatitude;
private String txLongitude;
/** 路段状态 1:启用,2:禁用 */
private Integer status;
/** 路段总车位数 */
private Integer totalParkingnum;
private Timestamp createTime;
private String parkFee;
private String planImg;
private String vrPath;
private String posJson;
@ApiModelProperty(value = "聚合支付类型 1 云纵 2 中科支付")
private Integer payType;
@ApiModelProperty(value = "中科商户号")
private String zkMchNo;
@ApiModelProperty(value = "中科应用id")
private String zkAppId;
@ApiModelProperty(value = "商户密钥key")
private String zkSecretKey;
@ApiModelProperty(value = "否 分账模式: 0-该笔订单不允许分账[默认], 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)")
private Integer zkDivisionMode;
private String showScreen;
private Integer isCharging;
private Integer chargingNum;
/**
* 剩余泊位数
*/
private Integer remainNum;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.service.dto;
import admin.annotation.Query;
import lombok.Data;
/**
* @website https://el-admin.vip
* @author wl
* @date 2020-07-12
**/
@Data
public class ParkingRoadInfoQueryCriteria{
/** 模糊 */
@Query(type = Query.Type.INNER_LIKE)
private String roadName;
@Query
private Long feeRuleId;
/** 模糊 */
@Query(type = Query.Type.INNER_LIKE)
private String roadSerial;
@Query
private Integer roadType;
/** 精确 */
@Query
private Integer status;
/** 精确 */
@Query
private Long roadId;
/** 精确 */
@Query
private Integer inUse;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.service.impl;
import admin.annotation.CurDataSource;
import admin.base.PageInfo;
import admin.base.QueryHelpMybatisPlus;
import admin.base.impl.CommonServiceImpl;
import admin.modules.parking.domain.ParkingInInfo;
import admin.modules.parking.service.ParkingInInfoService;
import admin.modules.parking.service.dto.ParkingInInfoDto;
import admin.modules.parking.service.dto.ParkingInInfoQueryCriteria;
import admin.modules.parking.service.mapper.ParkingInInfoMapper;
import admin.utils.ConvertUtil;
import admin.utils.PageUtil;
import admin.utils.ValidationUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @website https://el-admin.vip
* @description 服务实现
* @author wl
* @date 2020-10-06
**/
@Service
@RequiredArgsConstructor
@Slf4j
@CurDataSource("slaver")
public class ParkingInInfoServiceImpl extends CommonServiceImpl<ParkingInInfoMapper, ParkingInInfo> implements ParkingInInfoService {
private final ParkingInInfoMapper parkingInInfoMapper;
@Override
public PageInfo<ParkingInInfoDto> queryAll(ParkingInInfoQueryCriteria criteria, Pageable pageable){
IPage<ParkingInInfo> page = PageUtil.toMybatisPage(pageable, false);
IPage<ParkingInInfo> pageList = parkingInInfoMapper.selectPage(page, QueryHelpMybatisPlus.getPredicate(criteria));
return ConvertUtil.convertPage(pageList, ParkingInInfoDto.class);
}
@Override
public List<ParkingInInfoDto> queryAll(ParkingInInfoQueryCriteria criteria){
return ConvertUtil.convertList(parkingInInfoMapper.selectList(QueryHelpMybatisPlus.getPredicate(criteria)), ParkingInInfoDto.class);
}
@Override
@Transactional
public ParkingInInfoDto findById(Long parkingInId) {
ParkingInInfo parkingInInfo = parkingInInfoMapper.findById(parkingInId);
ValidationUtil.isNull(parkingInInfo.getParkingInId(),"ParkingInInfo","parkingInId",parkingInId);
return ConvertUtil.convert(parkingInInfo, ParkingInInfoDto.class);
}
@Override
public List<ParkingInInfoDto> findAll() {
List<ParkingInInfo> parkingInInfo = parkingInInfoMapper.findAll();
return ConvertUtil.convertList(parkingInInfo,ParkingInInfoDto.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ParkingInInfoDto create(ParkingInInfo resources) {
parkingInInfoMapper.insert(resources);
return ConvertUtil.convert(resources, ParkingInInfoDto.class);
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.parking.service.impl;
import admin.annotation.CurDataSource;
import admin.base.PageInfo;
import admin.base.QueryHelpMybatisPlus;
import admin.base.impl.CommonServiceImpl;
import admin.modules.parking.domain.ParkingRoadInfo;
import admin.modules.parking.service.ParkingRoadInfoService;
import admin.modules.parking.service.dto.ParkingRoadInfoDto;
import admin.modules.parking.service.dto.ParkingRoadInfoQueryCriteria;
import admin.modules.parking.service.mapper.ParkingRoadInfoMapper;
import admin.utils.ConvertUtil;
import admin.utils.FileUtil;
import admin.utils.PageUtil;
import admin.utils.ValidationUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.base.Stopwatch;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @website https://el-admin.vip
* @description 服务实现
* @author wl
* @date 2020-07-12
**/
@Service
@Slf4j
@RequiredArgsConstructor
public class ParkingRoadInfoServiceImpl extends CommonServiceImpl<ParkingRoadInfoMapper, ParkingRoadInfo> implements ParkingRoadInfoService {
private final ParkingRoadInfoMapper parkingRoadInfoMapper;
@Override
public PageInfo<ParkingRoadInfoDto> queryAll(ParkingRoadInfoQueryCriteria criteria, Pageable pageable){
IPage<ParkingRoadInfo> page = PageUtil.toMybatisPage(pageable, false);
IPage<ParkingRoadInfo> pageList = parkingRoadInfoMapper.selectPage(page, QueryHelpMybatisPlus.getPredicate(criteria));
return ConvertUtil.convertPage(pageList, ParkingRoadInfoDto.class);
}
@Override
public List<ParkingRoadInfoDto> queryAll(ParkingRoadInfoQueryCriteria criteria){
return ConvertUtil.convertList(parkingRoadInfoMapper.selectList(QueryHelpMybatisPlus.getPredicate(criteria)), ParkingRoadInfoDto.class);
}
@Override
@Transactional
public ParkingRoadInfoDto findById(Long roadId) {
ParkingRoadInfo parkingRoadInfo = parkingRoadInfoMapper.selectById(roadId);
ValidationUtil.isNull(parkingRoadInfo.getRoadId(),"ParkingRoadInfo","roadId",roadId);
return ConvertUtil.convert(parkingRoadInfo, ParkingRoadInfoDto.class);
}
@Override
public ParkingRoadInfo findByAreaIdAndRoadSerial(String areaId, String roadSerial) {
ParkingRoadInfo parkingRoadInfo = parkingRoadInfoMapper.findByAreaIdAndRoadSerial(areaId, roadSerial);
return parkingRoadInfo;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ParkingRoadInfoDto create(ParkingRoadInfo resources) {
parkingRoadInfoMapper.insert(resources);
return ConvertUtil.convert(resources, ParkingRoadInfoDto.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(ParkingRoadInfo resources) {
ParkingRoadInfo parkingRoadInfo = parkingRoadInfoMapper.selectById(resources.getRoadId());
ValidationUtil.isNull( parkingRoadInfo.getRoadId(),"ParkingRoadInfo","id",resources.getRoadId());
parkingRoadInfo.copy(resources);
parkingRoadInfoMapper.updateById(parkingRoadInfo);
}
@Override
public void deleteAll(Long[] ids) {
for (Long roadId : ids) {
parkingRoadInfoMapper.deleteById(roadId);
}
}
@Override
public void download(List<ParkingRoadInfoDto> all, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (ParkingRoadInfoDto parkingRoadInfo : all) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("路段名称", parkingRoadInfo.getRoadName());
map.put("路段编号", parkingRoadInfo.getRoadSerial());
map.put("省id", parkingRoadInfo.getProvinceId());
map.put(" provinceName", parkingRoadInfo.getProvinceName());
map.put(" cityId", parkingRoadInfo.getCityId());
map.put(" cityName", parkingRoadInfo.getCityName());
map.put(" areaId", parkingRoadInfo.getAreaId());
map.put(" areaName", parkingRoadInfo.getAreaName());
map.put(" address", parkingRoadInfo.getAddress());
map.put(" latitude", parkingRoadInfo.getLatitude());
map.put(" longitude", parkingRoadInfo.getLongitude());
map.put("路段状态 1:启用,2:禁用", parkingRoadInfo.getStatus());
map.put(" createTime", parkingRoadInfo.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
@Override
public Integer parkingInfoCount(Long roadId, Integer status) {
return parkingRoadInfoMapper.parkingInfoCount(roadId,status);
}
@Override
@CurDataSource("slaver")
public List<ParkingRoadInfoDto> getRoadInfo() {
Stopwatch stopwatch = Stopwatch.createStarted();
//1.获取所有路段信息
List<ParkingRoadInfoDto> roadInfoList = queryAll(null);
//2.归集
roadInfoList.forEach(i->{
i.setRemainNum(0);
});
log.info("查询路段剩余泊位数耗时{}毫秒",stopwatch.elapsed(TimeUnit.MILLISECONDS));
return roadInfoList;
}
}
package admin.modules.parking.service.mapper;
import admin.annotation.CurDataSource;
import admin.base.CommonMapper;
import admin.modules.parking.domain.ParkingInInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @description: ParkingInInfoMapper
* @date: 2023/7/24 16:04
* @author: wk
* @version: 1.0
*/
@Repository
@CurDataSource("slaver")
public interface ParkingInInfoMapper extends CommonMapper<ParkingInInfo> {
ParkingInInfo findByParkingInSerial(@Param("parkingInSerial") String parkingInSerial);
ParkingInInfo findByParentChannel(@Param("parent") Integer parent,
@Param("channelId") String channelId);
ParkingInInfo findByParkingInNo(@Param("parkingInNo") String parkingInNo);
Integer parkingCount();
@CurDataSource(value = "slaver")
ParkingInInfo findById(@Param("parkingInId") Long parkingInId);
List<ParkingInInfo> findAll();
}
package admin.modules.parking.service.mapper;
import admin.annotation.CurDataSource;
import admin.base.CommonMapper;
import admin.modules.parking.domain.ParkingInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @description: ParkingInfoMapper
* @date: 2023/7/24 16:03
* @author: wk
* @version: 1.0
*/
@Repository
public interface ParkingInfoMapper extends CommonMapper<ParkingInfo> {
ParkingInfo findByParkingSerial(@Param("parkingSerial") String parkingSerial);
@CurDataSource("slaver")
List<ParkingInfo> findByJobId(@Param("jobId") Long jobId);
List<ParkingInfo> findByRoadId(@Param("jobId") Long roadId);
List<Map<String,Object>> getRoadParkingCount(@Param("roadList") Set<Long> roadList);
Integer getOnlineParkingNum(@Param("status") Integer status);
List<Map<String,Object>> getParkingUseNum();
List<ParkingInfo> findByIdIn(@Param("idList") Set<Long> idList);
void modifyBatchJobMsg(List<Long> list);
Long findRemainCountByRoadId(Long roadId);
}
package admin.modules.parking.service.mapper;
import admin.base.CommonMapper;
import admin.modules.parking.domain.ParkingRoadInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* @description: ParkingRoadInfoMapper
* @date: 2023/8/11 13:54
* @author: wk
* @version: 1.0
*/
@Repository
public interface ParkingRoadInfoMapper extends CommonMapper<ParkingRoadInfo> {
List<Map<String,Object>> getParkingRoadUseData();
ParkingRoadInfo findByAreaIdAndRoadSerial(@Param("areaId") String areaId,
@Param("roadSerial") String roadSerial);
Integer parkingInfoCount(@Param("roadId") Long roadId, @Param("status") Integer status);
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.config;
import admin.modules.security.config.bean.LoginProperties;
import admin.modules.security.config.bean.SecurityProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @apiNote 配置文件转换Pojo类的 统一配置 类
* @date: 2020/6/10 19:04
*/
@Configuration
public class ConfigBeanConfiguration {
@Bean
@ConfigurationProperties(prefix = "login", ignoreUnknownFields = true)
public LoginProperties loginProperties() {
return new LoginProperties();
}
@Bean
@ConfigurationProperties(prefix = "jwt", ignoreUnknownFields = true)
public SecurityProperties securityProperties() {
return new SecurityProperties();
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.config;
import admin.annotation.AnonymousAccess;
import admin.modules.security.config.bean.SecurityProperties;
import admin.modules.security.security.JwtAccessDeniedHandler;
import admin.modules.security.security.JwtAuthenticationEntryPoint;
import admin.modules.security.security.TokenConfigurer;
import admin.modules.security.security.TokenProvider;
import admin.modules.security.service.OnlineUserService;
import admin.modules.security.service.UserCacheClean;
import admin.utils.enums.RequestMethodEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
/**
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url: @AnonymousAccess
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
// 获取匿名标记
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
httpSecurity
// 禁用 CSRF
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
.antMatchers("/wx/**").permitAll()
.antMatchers("/device/**").permitAll()
// .antMatchers("/api/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型
// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
Map<String, Set<String>> anonymousUrls = new HashMap<>(6);
Set<String> get = new HashSet<>();
Set<String> post = new HashSet<>();
Set<String> put = new HashSet<>();
Set<String> patch = new HashSet<>();
Set<String> delete = new HashSet<>();
Set<String> all = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
}
}
}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
return anonymousUrls;
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheClean);
}
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.config.bean;
import lombok.Data;
/**
* 登录验证码配置信息
*
* @date: 2020/6/10 18:53
*/
@Data
public class LoginCode {
/**
* 验证码配置
*/
private LoginCodeEnum codeType;
/**
* 验证码有效期 分钟
*/
private Long expiration = 2L;
/**
* 验证码内容长度
*/
private int length = 2;
/**
* 验证码宽度
*/
private int width = 111;
/**
* 验证码高度
*/
private int height = 36;
/**
* 验证码字体
*/
private String fontName;
/**
* 字体大小
*/
private int fontSize = 25;
public LoginCodeEnum getCodeType() {
return codeType;
}
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.config.bean;
/**
* 验证码配置枚举
*
* @date: 2020/6/10 17:40
*/
public enum LoginCodeEnum {
/**
* 算数
*/
arithmetic,
/**
* 中文
*/
chinese,
/**
* 中文闪图
*/
chinese_gif,
/**
* 闪图
*/
gif,
spec
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version loginCode.length.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-loginCode.length.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.config.bean;
import admin.exception.BadConfigurationException;
import admin.utils.StringUtils;
import com.wf.captcha.*;
import com.wf.captcha.base.Captcha;
import lombok.Data;
import java.awt.*;
import java.util.Objects;
/**
* 配置文件读取
*
* @author liaojinlong
* @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6
*/
@Data
public class LoginProperties {
/**
* 账号单用户 登录
*/
private boolean singleLogin = false;
private LoginCode loginCode;
/**
* 用户登录信息缓存
*/
private boolean cacheEnable;
public boolean isSingleLogin() {
return singleLogin;
}
public boolean isCacheEnable() {
return cacheEnable;
}
/**
* 获取验证码生产类
*
* @return /
*/
public Captcha getCaptcha() {
if (Objects.isNull(loginCode)) {
loginCode = new LoginCode();
if (Objects.isNull(loginCode.getCodeType())) {
loginCode.setCodeType(LoginCodeEnum.arithmetic);
}
}
return switchCaptcha(loginCode);
}
/**
* 依据配置信息生产验证码
*
* @param loginCode 验证码配置信息
* @return /
*/
private Captcha switchCaptcha(LoginCode loginCode) {
Captcha captcha;
synchronized (this) {
switch (loginCode.getCodeType()) {
case arithmetic:
// 算术类型 https://gitee.com/whvse/EasyCaptcha
captcha = new ArithmeticCaptcha(loginCode.getWidth(), loginCode.getHeight());
// 几位数运算,默认是两位
captcha.setLen(loginCode.getLength());
break;
case chinese:
captcha = new ChineseCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case chinese_gif:
captcha = new ChineseGifCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case gif:
captcha = new GifCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case spec:
captcha = new SpecCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
default:
throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
}
}
if(StringUtils.isNotBlank(loginCode.getFontName())){
captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize()));
}
return captcha;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.config.bean;
import lombok.Data;
/**
* Jwt参数配置
*
* @date 2019年11月28日
*/
@Data
public class SecurityProperties {
/**
* Request Headers : Authorization
*/
private String header;
/**
* 令牌前缀,最后留个空格 Bearer
*/
private String tokenStartWith;
/**
* 必须使用最少88位的Base64对该令牌进行编码
*/
private String base64Secret;
/**
* 令牌过期时间 此处单位/毫秒
*/
private Long tokenValidityInSeconds;
/**
* 在线用户 key,根据 key 查询 redis 中在线用户的数据
*/
private String onlineKey;
/**
* 验证码 key
*/
private String codeKey;
/**
* token 续期检查
*/
private Long detect;
/**
* 续期时间
*/
private Long renew;
public String getTokenStartWith() {
return tokenStartWith + " ";
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.security;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException==null?"Unauthorized":authException.getMessage());
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.security;
import admin.modules.security.config.bean.SecurityProperties;
import admin.modules.security.service.OnlineUserService;
import admin.modules.security.service.UserCacheClean;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author /
*/
@RequiredArgsConstructor
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheClean);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.security;
import admin.modules.security.config.bean.SecurityProperties;
import admin.modules.security.service.OnlineUserService;
import admin.modules.security.service.UserCacheClean;
import admin.modules.security.service.dto.OnlineUserDto;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.ExpiredJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
/**
* @author /
*/
public class TokenFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
/**
* @param tokenProvider Token
* @param properties JWT
* @param onlineUserService 用户在线
* @param userCacheClean 用户缓存清理工具
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheClean userCacheClean) {
this.properties = properties;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.userCacheClean = userCacheClean;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = resolveToken(httpServletRequest);
// 对于 Token 为空的不需要去查 Redis
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = null;
boolean cleanUserCache = false;
try {
onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
cleanUserCache = true;
} finally {
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
userCacheClean.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
}
}
if (onlineUserDto != null && StringUtils.hasText(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(token);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 初步检测Token
*
* @param request /
* @return /
*/
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token:{}", bearerToken);
}
return null;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.security;
import admin.modules.security.config.bean.SecurityProperties;
import admin.utils.RedisUtils;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author /
*/
@Slf4j
@Component
public class TokenProvider implements InitializingBean {
private final SecurityProperties properties;
private final RedisUtils redisUtils;
public static final String AUTHORITIES_KEY = "auth";
private JwtParser jwtParser;
private JwtBuilder jwtBuilder;
public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) {
this.properties = properties;
this.redisUtils = redisUtils;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
Key key = Keys.hmacShaKeyFor(keyBytes);
jwtParser = Jwts.parserBuilder()
.setSigningKey(key)
.build();
jwtBuilder = Jwts.builder()
.signWith(key, SignatureAlgorithm.HS512);
}
/**
* 创建Token 设置永不过期,
* Token 的时间有效性转到Redis 维护
*
* @param authentication /
* @return /
*/
public String createToken(Authentication authentication) {
/*
* 获取权限列表
*/
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return jwtBuilder
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.claim(AUTHORITIES_KEY, authorities)
.setSubject(authentication.getName())
.compact();
}
/**
* 依据Token 获取鉴权信息
*
* @param token /
* @return /
*/
Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
// fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
Collection<? extends GrantedAuthority> authorities =
ObjectUtil.isNotEmpty(authoritiesStr) ?
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
User principal = new User(claims.getSubject(), "******", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public Claims getClaims(String token) {
return jwtParser
.parseClaimsJws(token)
.getBody();
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.service;
import admin.modules.security.config.bean.SecurityProperties;
import admin.modules.security.service.dto.JwtUserDto;
import admin.modules.security.service.dto.OnlineUserDto;
import admin.utils.*;
import cn.hutool.core.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @date 2019年10月26日21:56:27
*/
@Service
@Slf4j
public class OnlineUserService {
private final SecurityProperties properties;
private final RedisUtils redisUtils;
public OnlineUserService(SecurityProperties properties, RedisUtils redisUtils) {
this.properties = properties;
this.redisUtils = redisUtils;
}
/**
* 保存在线用户信息
* @param jwtUserDto /
* @param token /
* @param request /
*/
public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request){
String dept = jwtUserDto.getUser().getDept().getName();
String ip = StringUtils.getIp(request);
String browser = StringUtils.getBrowser(request);
String address = StringUtils.getCityInfo(ip);
OnlineUserDto onlineUserDto = null;
try {
onlineUserDto = new OnlineUserDto(jwtUserDto.getUsername(), jwtUserDto.getUser().getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());
} catch (Exception e) {
log.error(e.getMessage(),e);
}
redisUtils.set(properties.getOnlineKey() + token, onlineUserDto, properties.getTokenValidityInSeconds()/1000);
}
/**
* 查询全部数据
* @param filter /
* @param pageable /
* @return /
*/
public Map<String,Object> getAll(String filter, Pageable pageable){
List<OnlineUserDto> onlineUserDtos = getAll(filter);
Map<String, Object> page = PageUtil.toPage(PageUtil.toPage(pageable.getPageNumber(), pageable.getPageSize(), onlineUserDtos), onlineUserDtos.size());
return BeanUtil.beanToMap(page);
}
/**
* 查询全部数据,不分页
* @param filter /
* @return /
*/
public List<OnlineUserDto> getAll(String filter){
List<String> keys = redisUtils.scan(properties.getOnlineKey() + "*");
Collections.reverse(keys);
List<OnlineUserDto> onlineUserDtos = new ArrayList<>();
for (String key : keys) {
OnlineUserDto onlineUserDto = (OnlineUserDto) redisUtils.get(key);
if(StringUtils.isNotBlank(filter)){
if(onlineUserDto.toString().contains(filter)){
onlineUserDtos.add(onlineUserDto);
}
} else {
onlineUserDtos.add(onlineUserDto);
}
}
onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime()));
return onlineUserDtos;
}
/**
* 踢出用户
* @param key /
*/
public void kickOut(String key){
key = properties.getOnlineKey() + key;
redisUtils.del(key);
}
/**
* 退出登录
* @param token /
*/
public void logout(String token) {
String key = properties.getOnlineKey() + token;
redisUtils.del(key);
}
/**
* 导出
* @param all /
* @param response /
* @throws IOException /
*/
public void download(List<OnlineUserDto> all, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (OnlineUserDto user : all) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("用户名", user.getUserName());
map.put("部门", user.getDept());
map.put("登录IP", user.getIp());
map.put("登录地点", user.getAddress());
map.put("浏览器", user.getBrowser());
map.put("登录日期", user.getLoginTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
/**
* 查询用户
* @param key /
* @return /
*/
public OnlineUserDto getOne(String key) {
return (OnlineUserDto)redisUtils.get(key);
}
/**
* 检测用户是否在之前已经登录,已经登录踢下线
* @param userName 用户名
*/
public void checkLoginOnUser(String userName, String igoreToken){
List<OnlineUserDto> onlineUserDtos = getAll(userName);
if(onlineUserDtos ==null || onlineUserDtos.isEmpty()){
return;
}
for(OnlineUserDto onlineUserDto : onlineUserDtos){
if(onlineUserDto.getUserName().equals(userName)){
try {
String token =EncryptUtils.desDecrypt(onlineUserDto.getKey());
if(StringUtils.isNotBlank(igoreToken)&&!igoreToken.equals(token)){
this.kickOut(token);
}else if(StringUtils.isBlank(igoreToken)){
this.kickOut(token);
}
} catch (Exception e) {
log.error("checkUser is error",e);
}
}
}
}
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.service;
import admin.utils.StringUtils;
import org.springframework.stereotype.Component;
/**
* @date: 2020/6/11 18:01
* @apiNote: 用于清理 用户登录信息缓存,为防止Spring循环依赖与安全考虑 ,单独构成工具类
*/
@Component
public class UserCacheClean {
/**
* 清理特定用户缓存信息<br>
* 用户信息变更时
*
* @param userName /
*/
public void cleanUserCache(String userName) {
if (StringUtils.isNotEmpty(userName)) {
UserDetailsServiceImpl.userDtoCache.remove(userName);
}
}
/**
* 清理所有用户的缓存信息<br>
* ,如发生角色授权信息变化,可以简便的全部失效缓存
*/
public void cleanAll() {
UserDetailsServiceImpl.userDtoCache.clear();
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.service;
import admin.exception.BadRequestException;
import admin.exception.EntityNotFoundException;
import admin.modules.security.config.bean.LoginProperties;
import admin.modules.security.service.dto.JwtUserDto;
import admin.modules.system.service.DataService;
import admin.modules.system.service.RoleService;
import admin.modules.system.service.UserService;
import admin.modules.system.service.dto.UserDto;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @date 2018-11-22
*/
@RequiredArgsConstructor
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
private final RoleService roleService;
private final DataService dataService;
private final LoginProperties loginProperties;
public void setEnableCache(boolean enableCache) {
this.loginProperties.setCacheEnable(enableCache);
}
/**
* 用户信息缓存
*
* @see {@link UserCacheClean}
*/
static Map<String, JwtUserDto> userDtoCache = new ConcurrentHashMap<>();
@Override
public JwtUserDto loadUserByUsername(String username) {
boolean searchDb = true;
JwtUserDto jwtUserDto = null;
if (loginProperties.isCacheEnable() && userDtoCache.containsKey(username)) {
jwtUserDto = userDtoCache.get(username);
searchDb = false;
}
if (searchDb) {
UserDto user;
try {
user = userService.findByName(username);
} catch (EntityNotFoundException e) {
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
throw new UsernameNotFoundException("", e);
}
if (user == null) {
throw new UsernameNotFoundException("");
} else {
if (!user.getEnabled()) {
throw new BadRequestException("账号未激活");
}
jwtUserDto = new JwtUserDto("",
user,
dataService.getDeptIds(user),
roleService.mapToGrantedAuthorities(user)
);
userDtoCache.put(username, jwtUserDto);
}
}
return jwtUserDto;
}
public UserDto getUserByPhone(String phone){
UserDto user;
try {
user = userService.findByPhone(phone);
} catch (EntityNotFoundException e) {
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
throw new UsernameNotFoundException("", e);
}
return user;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.service.dto;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* @date 2018-11-30
*/
@Getter
@Setter
public class AuthUserDto {
@NotBlank
private String username;
@NotBlank
private String password;
private String code;
private String uuid = "";
private String phone="";
/** 手持机序列号 */
private String handSerial;
@Override
public String toString() {
return "{username=" + username + ", password= ******}";
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.service.dto;
import admin.modules.system.service.dto.UserDto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @date 2018-11-23
*/
@Getter
@AllArgsConstructor
public class JwtUserDto implements UserDetails {
private String token;
public void setToken(String token){
this.token=token;
}
private final UserDto user;
private final List<Long> dataScopes;
@JsonIgnore
private final List<GrantedAuthority> authorities;
public Set<String> getRoles() {
return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
}
@Override
@JsonIgnore
public String getPassword() {
return user.getPassword();
}
@Override
@JsonIgnore
public String getUsername() {
return user.getUsername();
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isEnabled() {
return user.getEnabled();
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.security.service.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 在线用户
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlineUserDto implements Serializable {
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
/**
* 岗位
*/
private String dept;
/**
* 浏览器
*/
private String browser;
/**
* IP
*/
private String ip;
/**
* 地址
*/
private String address;
/**
* token
*/
private String key;
/**
* 登录时间
*/
private Date loginTime;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.system.domain;
import admin.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
/**
* @date 2019-03-25
*/
//@Entity
@Getter
@Setter
//@Table(name="sys_dept")
@TableName("sys_dept")
public class Dept extends BaseEntity implements Serializable {
@Id
// @Column(name = "dept_id")
@NotNull(groups = Update.class)
@TableId(value="dept_id", type= IdType.AUTO)
@ApiModelProperty(value = "ID", hidden = true)
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@TableField(exist = false)
@JsonIgnore
// @ManyToMany(mappedBy = "depts")
@ApiModelProperty(value = "角色")
private Set<Role> roles;
@ApiModelProperty(value = "排序")
@TableField(value = "dept_sort")
private Integer deptSort;
@TableField(value = "name")
@NotBlank
@ApiModelProperty(value = "部门名称")
private String name;
@TableField(value = "enabled")
@NotNull
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@TableField(value = "pid")
@ApiModelProperty(value = "上级部门")
private Long pid;
@TableField(value = "sub_count")
@ApiModelProperty(value = "子节点数目", hidden = true)
private Integer subCount = 0;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Dept dept = (Dept) o;
return Objects.equals(id, dept.id) &&
Objects.equals(name, dept.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.system.domain;
import admin.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* @date 2019-04-10
*/
//@Entity
@Getter
@Setter
//@Table(name="sys_dict")
@TableName("sys_dict")
public class Dict extends BaseEntity implements Serializable {
@Id
// @Column(name = "dict_id")
@TableId(value="dict_id", type= IdType.AUTO)
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// @OneToMany(mappedBy = "dict",cascade={CascadeType.PERSIST,CascadeType.REMOVE})
@TableField(exist = false)
private List<DictDetail> dictDetails;
@TableField(value = "name")
@NotBlank
@ApiModelProperty(value = "名称")
private String name;
@TableField(value = "description")
@ApiModelProperty(value = "描述")
private String description;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.system.domain;
import admin.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* @date 2019-04-10
*/
//@Entity
@Getter
@Setter
//@Table(name="sys_dict_detail")
@TableName("sys_dict_detail")
public class DictDetail extends BaseEntity implements Serializable {
@Id
// @Column(name = "detail_id")
@TableId(value="detail_id", type= IdType.AUTO)
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@TableField(value = "dict_id")
private Long dictId;
@TableField(exist = false)
// @JoinColumn(name = "dict_id")
// @ManyToOne(fetch=FetchType.LAZY)
@ApiModelProperty(value = "字典", hidden = true)
private Dict dict;
@ApiModelProperty(value = "字典标签")
@TableField(value = "label")
private String label;
@ApiModelProperty(value = "小程序字典key")
@TableField(value = "xcx_key")
private String xcxKey;
@ApiModelProperty(value = "字典值")
@TableField(value = "value")
private String value;
@ApiModelProperty(value = "排序")
@TableField(value = "dict_sort")
private Integer dictSort = 999;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.system.domain;
import admin.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
/**
* @date 2019-03-29
*/
//@Entity
@Getter
@Setter
//@Table(name="sys_job")
@TableName("sys_job")
public class Job extends BaseEntity implements Serializable {
@Id
// @Column(name = "job_id")
@TableId(value="job_id", type= IdType.AUTO)
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ApiModelProperty(value = "岗位名称")
@TableField(value = "name")
private String name;
@ApiModelProperty(value = "岗位编号")
@TableField(value = "job_serial")
private String jobSerial;
@ApiModelProperty(value = "路段id")
@TableField(value = "parking_road_id")
private String parkingRoadId;
@NotBlank
@ApiModelProperty(value = "路段名称")
@TableField(value = "parking_road_name")
private String parkingRoadName;
@ApiModelProperty(value = "车位数")
@TableField(value = "parking_num")
private Integer parkingNum;
// @NotNull
@ApiModelProperty(value = "岗位排序")
@TableField(value = "job_sort")
private Long jobSort=99L;
@NotNull
@ApiModelProperty(value = "是否启用")
@TableField(value = "enabled")
private Boolean enabled=true;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Job job = (Job) o;
return Objects.equals(id, job.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
package admin.modules.system.domain;
import admin.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 会员数据,小程序登录绑定手机号码用户信息
* */
//@Entity
@Getter
@Setter
//@Table(name="sys_member")
@TableName("sys_member")
public class Member {
@Id
// @Column(name = "member_id")
@TableId(value="member_id", type= IdType.AUTO)
@NotNull(groups = BaseEntity.Update.class)
// @GeneratedValue(strategy = GenerationType.IDENTITY)
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@TableField(value = "wx_token")
@NotBlank
@ApiModelProperty(value = "微信token")
private String wxToken;
}
This diff is collapsed.
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package admin.modules.system.domain;
import admin.base.BaseEntity;
import admin.utils.enums.DataScopeEnum;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Set;
/**
* 角色
*
* @date 2018-11-22
*/
@Data
//@Table(name = "sys_role")
@TableName("sys_role")
public class Role extends BaseEntity implements Serializable {
@TableId(value = "role_id", type = IdType.AUTO)
// @NotNull(groups = {Update.class})
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@TableField(exist = false)
@ApiModelProperty(value = "用户", hidden = true)
private Set<User> users;
@TableField(exist = false)
@ApiModelProperty(value = "菜单", hidden = true)
private Set<Menu> menus;
@TableField(exist = false)
@ApiModelProperty(value = "部门", hidden = true)
private Set<Dept> depts;
@TableField(value = "name")
@NotBlank
@ApiModelProperty(value = "名称", hidden = true)
private String name;
@TableField(value = "data_scope")
@ApiModelProperty(value = "数据权限,全部 、 本级 、 自定义")
private String dataScope = DataScopeEnum.THIS_LEVEL.getValue();
// @Column(name = "level")
@TableField(value = "level")
@ApiModelProperty(value = "级别,数值越小,级别越大")
private Integer level = 3;
@TableField(value = "description")
@ApiModelProperty(value = "描述")
private String description;
}
This diff is collapsed.
This diff is collapsed.
package admin.modules.system.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @description: SysUsersRoles
* @date: 2023/7/27 15:47
* @author: wk
* @version: 1.0
*/
@Data
@TableName("sys_roles_depts")
public class SysRolesDepts {
@TableField(value = "role_id")
private Long roleId;
@TableField(value = "dept_id")
private Long deptId;
}
package admin.modules.system.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @description: SysUsersRoles
* @date: 2023/7/27 15:47
* @author: wk
* @version: 1.0
*/
@Data
@TableName("sys_roles_menus")
public class SysRolesMenus {
@TableField(value = "menu_id")
private Long menuId;
@TableField(value = "role_id")
private Long roleId;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment