Compare commits

...

3 Commits

Author SHA1 Message Date
cuianbing
08e4f05233 合并远程仓库 2026-01-02 10:13:35 +08:00
cuianbing
cdf19f2314 基本完成 2026-01-02 10:08:57 +08:00
5990a4f052 init 2025-12-30 11:02:57 +08:00
63 changed files with 3398 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

20
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="BUILD_PROCESS_HEAP_SIZE" value="2048" />
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<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="rensijin-cchs-server" />
</profile>
</annotationProcessing>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="rensijin-cchs-server" options="-parameters" />
</option>
</component>
</project>

6
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?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>

30
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</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>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="http://nexus.lg.china-yongfeng.com/repository/maven-public/" />
</remote-repository>
<remote-repository>
<option name="id" value="g-siba0859-EasyJava-bobo" />
<option name="name" value="bobo" />
<option name="url" value="https://g-siba0859-maven.pkg.coding.net/repository/EasyJava/bobo/" />
</remote-repository>
</component>
</project>

12
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?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>

10
.idea/sonarlint.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SonarLintProjectSettings">
<option name="moduleMapping">
<map>
<entry key="rensijin-cchs" value="SpringBoot" />
</map>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

136
pom.xml Normal file
View File

@@ -0,0 +1,136 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/>
</parent>
<groupId>com.rensijin.cchs</groupId>
<artifactId>rensijin-cchs-server</artifactId>
<version>1.0.0</version>
<name>rensijin-cchs</name>
<description>任思瑾的客户投诉处理系统</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!--图片验证码-->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.50</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.5.RELEASE</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<!--测试包下的代码出错不影响项目的编译-->
<testFailureIgnore>true</testFailureIgnore>
<!--跳过测试包代码-->
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
<finalName>EasyJavaTemplate</finalName>
</build>
</project>

View File

@@ -0,0 +1,19 @@
package cn.rensijin.cchs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @desc系统启动类
*/
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
System.out.println("**************************************");
System.out.println("**************系统启动成功**************");
System.out.println("**************************************");
}
}

View File

@@ -0,0 +1,13 @@
package cn.rensijin.cchs.annotation;
import java.lang.annotation.*;
/**
* @Desc: 忽略Token验证
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreAuth {
}

View File

@@ -0,0 +1,25 @@
package cn.rensijin.cchs.annotation;
import java.lang.annotation.*;
/**
* 用于防刷限流的注解
* 默认是5秒内只能调用一次
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/** 限流的key */
String key() default "limit:";
/** 周期,单位是秒 */
int cycle() default 5;
/** 请求次数 */
int count() default 1;
/** 默认提示信息 */
String msg() default "请勿重复点击";
}

View File

@@ -0,0 +1,39 @@
package cn.rensijin.cchs.config.cors;
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;
/**
* 跨域配置
*/
@Configuration
public class CorsFilterConfig {
@Bean
public CorsFilter corsFilter() {
//1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//1) 允许的域(*表示允许所有)
config.addAllowedOriginPattern("*");
//2) 是否发送Cookie信息
config.setAllowCredentials(true);
//3) 允许的请求方式
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
// 4允许的头信息
config.addAllowedHeader("*");
//2.添加映射路径,我们拦截一切请求
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}

View File

@@ -0,0 +1,78 @@
package cn.rensijin.cchs.config.date;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @description 全局时间配置类
*/
@Configuration
public class DateConfig {
private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private static final String DATE_PATTERN = "yyyy-MM-dd";
/**
* string转localdate
*/
@Bean
public Converter<String, LocalDate> localDateConverter() {
return new Converter<String, LocalDate>() {
@Override
public LocalDate convert(String source) {
if (source.trim().length() == 0)
return null;
try {
return LocalDate.parse(source);
} catch (Exception e) {
return LocalDate.parse(source, DateTimeFormatter.ofPattern(DATE_PATTERN));
}
}
};
}
/**
* string转localdatetime
*/
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String source) {
if (source.trim().length() == 0)
return null;
// 先尝试ISO格式: 2019-07-15T16:00:00
try {
return LocalDateTime.parse(source);
} catch (Exception e) {
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DATE_TIME_PATTERN));
}
}
};
}
/**
* 统一配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
JavaTimeModule module = new JavaTimeModule();
LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
return builder -> {
builder.simpleDateFormat(DATE_TIME_PATTERN);
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_PATTERN)));
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));
builder.modules(module);
};
}
}

View File

@@ -0,0 +1,49 @@
package cn.rensijin.cchs.config.date;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* 对象映射器:基于jackson将Java对象转为json或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DATE_TIME_FORMAT_NO_SECOND = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}

View File

@@ -0,0 +1,23 @@
package cn.rensijin.cchs.config.mp;
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;
/**
* @description: MyBatiesPlus配置类
*/
@Configuration
@MapperScan("cn.rensijin.cchs.mapper")
public class MyBatiesPlusConfiguration {
/*
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}

View File

@@ -0,0 +1,39 @@
package cn.rensijin.cchs.config.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* <p>Redis配置类</p>
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
// 设置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);// key序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

View File

@@ -0,0 +1,79 @@
package cn.rensijin.cchs.config.swagger;
import io.swagger.models.auth.In;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
/**
* Swagger接口文档配置类
*/
@Configuration
@EnableSwagger2
public class Swagger2 {
//默认路径http://127.0.0.1:port/swagger-ui.html
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// 是否启用Swagger
.enable(true)
// 用来创建该API的基本信息展示在文档的页面中自定义展示的信息
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
//.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))//扫描所有有注解的api
//.apis(RequestHandlerSelectors.any())//扫描所有
.apis(RequestHandlerSelectors.basePackage("cn.rensijin.cchs.controller"))//扫描包
.paths(PathSelectors.any())
.build()
/* 设置安全模式swagger可以设置访问token */
.securitySchemes(securitySchemes());
}
/**
* 安全模式这里指定token通过头请求头传递
*/
private List<SecurityScheme> securitySchemes()
{
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("token", "token", In.HEADER.toValue()));
return apiKeyList;
}
/**
* 默认的安全上引用
*/
private List<SecurityReference> defaultAuth()
{
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("token", authorizationScopes));
return securityReferences;
}
private ApiInfo apiInfo() {
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("标题:接口文档")
// 描述
.description("描述:用户前后端联调接口文档")
// 作者信息
.contact(new Contact("任思瑾", null, ""))
// 版本
.version("版本号:1.0.0")
.build();
}
}

View File

@@ -0,0 +1,58 @@
package cn.rensijin.cchs.config.web;
import cn.rensijin.cchs.config.date.JacksonObjectMapper;
import cn.rensijin.cchs.interceptor.LoginInterceptor;
import cn.rensijin.cchs.interceptor.RateLimitInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import javax.annotation.Resource;
import java.util.List;
/**
* web配置
*/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Resource
private LoginInterceptor loginInterceptor;
@Resource
private RateLimitInterceptor rateLimitInterceptor;
//扩展SpringMVC框架的消息转化器例如实现返回日期时间的格式化
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//将java对象序列化为json数据,为消息转换器设置序列转换器
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入容器中
converters.add(0, converter);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor);
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/", "/static/**", "/file/download/**")
.excludePathPatterns("/login/**", "/user/info", "/test/**","/user/register");
}
/**
* springboot 2.0配置WebMvcConfigurationSupport之后会导致默认配置被覆盖要访问静态资源需要重写addResourceHandlers方法
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/resources/")
.addResourceLocations("classpath:/static/");
super.addResourceHandlers(registry);
}
}

View File

@@ -0,0 +1,44 @@
package cn.rensijin.cchs.constants;
/**
* 系统错误码
*/
public enum ErrorCode {
SYSTEM_SUCCESS("0000", "操作成功!"),
ERROR_CODE_1001("1001", "操作失败,请稍候再试!"),
ERROR_CODE_1002("1002", "认证状态失效,请重新登录!"),
ERROR_CODE_1003("1003", "登录账号不能为空!"),
ERROR_CODE_1004("1004", "登录密码不能为空!"),
ERROR_CODE_1005("1005", "登录账号不存在!"),
ERROR_CODE_1006("1006", "登录账号存在多个!"),
ERROR_CODE_1007("1007", "登录密码错误!"),
ERROR_CODE_1008("1008", "Token错误或失效"),
ERROR_CODE_1009("1009", "UUID不能为空"),
ERROR_CODE_1010("1010", "图片验证码不能为空!"),
ERROR_CODE_1011("1011", "图片验证码已过期,请点击图片刷新!"),
ERROR_CODE_1012("1012", "图片验证码错误,请重新输入!"),
SYSTEM_ERROR("9999", "系统开小差了,请稍后再试!");
//错误码
private String code;
//错误信息
private String message;
ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,51 @@
package cn.rensijin.cchs.constants;
/**
* @description 系统常量类
*/
public class SystemConstants {
/**
* APP端保存注册短信验证码key
*/
public static final String TOKEN_PHONE_KEY_REGISTER = "app_sms_code_register:";
/**
* 令牌在Redis中保存时长
*/
public static final int TOKEN_REDIS_ALIVE_TIME = 2;
/**
* APP端保存短信验证码key
*/
public static final String TOKEN_PHONE_KEY = "app_sms_code:";
/**
* 登录时图片验证码存Redis的超时时间单位分钟
*/
public final static int LOGIN_CAPTCHA_EXPIRATION = 2;
/**
* 登录时图片验证码存Redis的key前缀
*/
public static final String CAPTCHA_CODE_KEY = "captcha_code:";
/**
* 文件存储路径(项目跟路径下面的files文件夹)
*/
public static final String FILES_DIR = "/files/";
/**
* APP用户登录 redis key
*/
public static final String LOGIN_TOKEN_KEY_APP = "login_app_tokens:";
//登录成功之后token超时时间单位分钟
public final static int LOGIN_TIME_OUT = 60;
//新用户注册的默认登录密码
public final static String DEFAULT_USER_LOGIN_PWD = "123456";
//新用户注册的默认头像
public final static String DEFAULT_USER_AVATAR = "https://easyjava.oss-cn-chengdu.aliyuncs.com/common/0161a7079e574da287dc182bdf756abc.jpg";
}

View File

@@ -0,0 +1,134 @@
package cn.rensijin.cchs.controller;
import cn.hutool.crypto.SecureUtil;
import cn.rensijin.cchs.annotation.IgnoreAuth;
import cn.rensijin.cchs.constants.ErrorCode;
import cn.rensijin.cchs.constants.SystemConstants;
import cn.rensijin.cchs.domain.system.SysUser;
import cn.rensijin.cchs.exception.MyException;
import cn.rensijin.cchs.mapper.system.SysUserMapper;
import cn.rensijin.cchs.result.JSONResult;
import cn.rensijin.cchs.util.MyTools;
import cn.rensijin.cchs.vo.UserTokenVo;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @desc 系统登录API
*/
@RestController
@RequestMapping("/login")
public class LoginController {
@Resource
private SysUserMapper sysUserMapper;
@Resource
private RedisTemplate redisTemplate;
/**
* 登录页面获取图片验证码
*/
@IgnoreAuth
@GetMapping("/getLoginImageCode/{uuid}")
public JSONResult getLoginImageCode(@PathVariable("uuid") String uuid) {
// 验证码内容存Redis的key值
String verifyKey = SystemConstants.CAPTCHA_CODE_KEY + uuid;
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
//设置验证码的类型(可不设置,使用默认值)
specCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER);
String code = specCaptcha.text().toLowerCase();
//图片验证码存Redis
redisTemplate.opsForValue().set(verifyKey, code, SystemConstants.LOGIN_CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
return JSONResult.success(specCaptcha.toBase64());
}
/**
* 系统登录接口
*
* @param user
* @return
*/
@IgnoreAuth
@PostMapping("/login")
public JSONResult login(@RequestBody SysUser user) {
//入参校验
if (!MyTools.hasLength(user.getUsername())) {
throw new MyException(ErrorCode.ERROR_CODE_1003);
}
if (!MyTools.hasLength(user.getPassword())) {
throw new MyException(ErrorCode.ERROR_CODE_1004);
}
if (!MyTools.hasLength(user.getUuid())) {
throw new MyException(ErrorCode.ERROR_CODE_1009);
}
if (!MyTools.hasLength(user.getImageCode())) {
throw new MyException(ErrorCode.ERROR_CODE_1010);
}
//到Redis获取验证码
String verifyKey = SystemConstants.CAPTCHA_CODE_KEY + user.getUuid();
String imagesCodeFromRedis = (String) redisTemplate.opsForValue().get(verifyKey);
if (!MyTools.hasLength(imagesCodeFromRedis)) {
throw new MyException(ErrorCode.ERROR_CODE_1011);
}
//开始对比图片验证码
if (!imagesCodeFromRedis.equals(user.getImageCode())) {
throw new MyException(ErrorCode.ERROR_CODE_1012);
}
//查看账号是否存在
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_name", user.getUsername());
List<SysUser> sysUserList = sysUserMapper.selectList(queryWrapper);
if (CollectionUtils.isEmpty(sysUserList)) {
throw new MyException(ErrorCode.ERROR_CODE_1005);
}
if (sysUserList.size() > 1) {
throw new MyException(ErrorCode.ERROR_CODE_1006);
}
//开始验证密码是否正确
SysUser sysUser = sysUserList.get(0);
if (!sysUser.getPassword().equalsIgnoreCase(SecureUtil.md5(user.getPassword()))) {
throw new MyException(ErrorCode.ERROR_CODE_1007);
}
//用户信息存Redis
String token = MyTools.getLoginToken();
//用户信息存储到Redis
redisTemplate.opsForValue().set(SystemConstants.LOGIN_TOKEN_KEY_APP + token, JSONObject.toJSONString(sysUser), SystemConstants.LOGIN_TIME_OUT, TimeUnit.MINUTES);
//返回token及相关用户信息给前端
UserTokenVo vo = new UserTokenVo();
vo.setToken(token);
vo.setAvatar(sysUser.getAvatar());
vo.setUserName(sysUser.getUsername());
vo.setRealName(sysUser.getRealName());
return JSONResult.success(vo);
}
/**
* 退出系统
*
* @param token
* @return
*/
@GetMapping("/logout")
public JSONResult getUSerInfo(@RequestParam("token") String token) {
redisTemplate.delete(token);
return JSONResult.success();
}
}

View File

@@ -0,0 +1,54 @@
package cn.rensijin.cchs.controller;
import cn.hutool.crypto.SecureUtil;
import cn.rensijin.cchs.annotation.IgnoreAuth;
import cn.rensijin.cchs.domain.system.SysUser;
import cn.rensijin.cchs.exception.MyException;
import cn.rensijin.cchs.result.JSONResult;
import cn.rensijin.cchs.service.system.SysUserService;
import cn.rensijin.cchs.util.MyTools;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @desc 用户注册
*/
@RestController
@RequestMapping("/user")
public class RegisterController {
@Autowired
private SysUserService sysUserService;
@IgnoreAuth
@PostMapping("/register")
public JSONResult login(@RequestBody SysUser user) {
if (!MyTools.hasLength(user.getUsername())) {
throw new MyException("账号不能为空!");
}
System.out.println(user);
//查询用户名是否被注册
QueryWrapper<SysUser> queryWrapper = new QueryWrapper();
queryWrapper.eq("user_name", user.getUsername());
SysUser one = sysUserService.getOne(queryWrapper);
if (one != null) {
throw new MyException("账号已被注册,请更换!");
}
SysUser sysUser = new SysUser();
BeanUtils.copyProperties(user, sysUser);
sysUser.setPassword(SecureUtil.md5(user.getPassword()));
sysUser.setAvatar("https://20241211oss.oss-cn-beijing.aliyuncs.com/202412/NewsApp/image/3_20241211143629.png");
//数据入库
System.out.println(sysUser);
sysUserService.save(sysUser);
return JSONResult.successMessage(true, "注册成功,快去登录吧!");
}
}

View File

@@ -0,0 +1,24 @@
package cn.rensijin.cchs.controller;
import cn.rensijin.cchs.annotation.RateLimit;
import cn.rensijin.cchs.result.JSONResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>测试类</p>
*
*/
@RestController
@RequestMapping("/test")
public class TestController {
//cycle秒内只能访问count次
@RateLimit(key= "testLimit:", count = 2, cycle = 2, msg = "同志,不要请求这么快,好吗")
@GetMapping("/test001")
public JSONResult test001() {
System.out.println("成功发送一条短信");
return JSONResult.success("成功发送一条短信");
}
}

View File

@@ -0,0 +1,138 @@
package cn.rensijin.cchs.controller.app;
import cn.rensijin.cchs.domain.app.AppSuggestion;
import cn.rensijin.cchs.result.PageList;
import cn.rensijin.cchs.service.app.AppSuggestionService;
import cn.rensijin.cchs.query.app.AppSuggestionQuery;
import cn.rensijin.cchs.controller.base.BaseController;
import cn.rensijin.cchs.util.DateUtils;
import cn.rensijin.cchs.result.JSONResult;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import java.util.Arrays;
import java.util.List;
/**
* 意见反馈表 API接口
*/
@RestController
@RequestMapping("/appSuggestion")
@Api(value = "/appSuggestion", tags = {"意见反馈表 API接口"})
public class AppSuggestionController extends BaseController{
@Autowired
public AppSuggestionService appSuggestionService;
/**
* 新增数据到【意见反馈表】
*/
@PostMapping(value="/save")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "AppSuggestion", name = "appSuggestion", value = "")
})
@ApiOperation(value = "新增数据到【意见反馈表】", notes = "新增数据到【意见反馈表】", httpMethod = "POST")
public JSONResult save(@RequestBody AppSuggestion appSuggestion){
appSuggestion.setCreateTime(DateUtils.getCurrentLocalDateTime());
// 获取当前用户真实姓名,如果获取失败则使用默认值
String currentUserName = getCurrentUserRealName();
if (currentUserName != null && !currentUserName.trim().isEmpty()) {
appSuggestion.setCreateUserName(currentUserName);
} else {
appSuggestion.setCreateUserName("匿名用户"); // 或者根据业务需求设置其他默认值
}
appSuggestion.setAppUserId(getCurrentUserId());
appSuggestion.setStatus("有效"); // 默认为有效状态
appSuggestionService.save(appSuggestion);
return JSONResult.success(true);
}
/**
* 修改【意见反馈表】表数据
*/
@PostMapping(value="/update")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "AppSuggestion", name = "appSuggestion", value = "")
})
@ApiOperation(value = "修改【意见反馈表】表数据", notes = "修改【意见反馈表】表数据", httpMethod = "POST")
public JSONResult update(@RequestBody AppSuggestion appSuggestion){
appSuggestion.setUpdateUserName(getCurrentUserRealName());
appSuggestionService.updateById(appSuggestion);
return JSONResult.success(true);
}
/**
* 批量删除【意见反馈表】数据
*/
@PostMapping(value="/batchDelete")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "AppSuggestionQuery", name = "query", value = "")
})
@ApiOperation(value = "批量删除【意见反馈表】数据", notes = "批量删除【意见反馈表】数据", httpMethod = "POST")
public JSONResult batchDelete(@RequestBody AppSuggestionQuery query){
//批量删除数据库数据
appSuggestionService.removeByIds(Arrays.asList(query.getIds()));
return JSONResult.success(true);
}
/**
* 单个删除【意见反馈表】数据
*/
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", dataType = "long", name = "id", value = "")
})
@ApiOperation(value = "单个删除【意见反馈表】数据", notes = "单个删除【意见反馈表】数据", httpMethod = "DELETE")
@DeleteMapping("/singleDelete/{id}")
public JSONResult batchDelete(@PathVariable("id") Long id){
//单个删除数据库数据
appSuggestionService.removeById(id);
return JSONResult.success(true);
}
/**
* 根据ID查询【意见反馈表】详情数据
*/
@GetMapping(value = "/{id}")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", dataType = "long", name = "id", value = "")
})
@ApiOperation(value = "根据ID查询【意见反馈表】详情数据", notes = "根据ID查询【意见反馈表】详情数据", httpMethod = "GET")
public JSONResult get(@PathVariable("id")Long id){
return JSONResult.success(appSuggestionService.getById(id));
}
/**
* 查询【意见反馈表】所有数据(不分页)
*/
@GetMapping(value = "/list")
@ApiOperation(value = "查询【意见反馈表】所有数据(不分页)", notes = "查询【意见反馈表】所有数据(不分页)", httpMethod = "GET")
public JSONResult list(){
QueryWrapper<AppSuggestion> queryWrapper = new QueryWrapper();
queryWrapper.orderByDesc("id");
List<AppSuggestion> list = appSuggestionService.list(queryWrapper);
return JSONResult.success(list);
}
/**
* 查询【意见反馈表】数据(分页)
* @param query 查询对象
* @return PageList 分页对象
*/
@PostMapping(value = "/pagelist")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "AppSuggestionQuery", name = "query", value = "查询对象")
})
@ApiOperation(value = "查询【意见反馈表】数据(分页)", notes = "查询【意见反馈表】数据(分页)", httpMethod = "POST")
public JSONResult pagelist(@RequestBody AppSuggestionQuery query){
Page<AppSuggestion> page = appSuggestionService.selectMySqlPage(query);
return JSONResult.success(new PageList<>(page.getTotal(), page.getRecords()));
}
}

View File

@@ -0,0 +1,65 @@
package cn.rensijin.cchs.controller.base;
import cn.rensijin.cchs.constants.ErrorCode;
import cn.rensijin.cchs.constants.SystemConstants;
import cn.rensijin.cchs.domain.system.SysUser;
import cn.rensijin.cchs.exception.MyException;
import cn.rensijin.cchs.util.MyTools;
import cn.rensijin.cchs.util.RedisCache;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* @des: Controller的基础类
*/
public class BaseController {
@Resource
private RedisCache redisCache;
/**
* 获取登录人的信息
*
* @return
*/
public SysUser getCurrentUser() {
//获取 HttpServletRequest 对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
//从请求头中获取 token 值
String token = request.getHeader("token");
if (!MyTools.hasLength(token)) {
throw new MyException(ErrorCode.ERROR_CODE_1002.getMessage());
}
String userInfoString = redisCache.getCacheObject(SystemConstants.LOGIN_TOKEN_KEY_APP + token);
if (!MyTools.hasLength(userInfoString)) {
throw new MyException(ErrorCode.ERROR_CODE_1002.getMessage());
}
SysUser sysUser = JSONObject.parseObject(userInfoString, SysUser.class);
return sysUser;
}
/**
* 获取登录人的用户ID
*
* @return
*/
public Long getCurrentUserId() {
return getCurrentUser().getId();
}
/**
* 获取登录人的真实姓名
*
* @return
*/
public String getCurrentUserRealName() {
return getCurrentUser().getRealName();
}
}

View File

@@ -0,0 +1,144 @@
package cn.rensijin.cchs.controller.system;
import cn.hutool.crypto.SecureUtil;
import cn.rensijin.cchs.constants.SystemConstants;
import cn.rensijin.cchs.controller.base.BaseController;
import cn.rensijin.cchs.domain.system.SysUser;
import cn.rensijin.cchs.query.system.SysUserQuery;
import cn.rensijin.cchs.result.JSONResult;
import cn.rensijin.cchs.result.PageList;
import cn.rensijin.cchs.service.system.SysUserService;
import cn.rensijin.cchs.util.DateUtils;
import cn.rensijin.cchs.util.MyTools;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
/**
* @desc 用户信息表 API接口
*/
@RestController
@RequestMapping("/user")
@Api(value = "/user", tags = {"用户信息表 API接口"})
public class SysUserController extends BaseController{
@Resource
public SysUserService sysUserService;
/**
* 新增数据到【用户信息表】
*/
@PostMapping(value="/save")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "SysUser", name = "sysUser", value = "")
})
@ApiOperation(value = "新增数据到【用户信息表】", notes = "新增数据到【用户信息表】", httpMethod = "POST")
public JSONResult save(@RequestBody SysUser sysUser){
sysUser.setCreateTime(DateUtils.getCurrentLocalDateTime());
sysUser.setCreateUserId(getCurrentUserId());
sysUser.setPassword(SecureUtil.md5(sysUser.getPassword()));//密码加密
if(!MyTools.hasLength(sysUser.getAvatar())){
sysUser.setAvatar(SystemConstants.DEFAULT_USER_AVATAR);//默认头像
}
sysUserService.save(sysUser);
return JSONResult.success(true);
}
/**
* 修改【用户信息表】表数据
*/
@PostMapping(value="/update")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "SysUser", name = "sysUser", value = "")
})
@ApiOperation(value = "修改【用户信息表】表数据", notes = "修改【用户信息表】表数据", httpMethod = "POST")
public JSONResult update(@RequestBody SysUser sysUser){
sysUser.setUpdateTime(DateUtils.getCurrentLocalDateTime());
sysUser.setUpdateUserId(getCurrentUserId());
sysUserService.updateById(sysUser);
return JSONResult.success(true);
}
/**
* 批量删除【用户信息表】数据
*/
@PostMapping(value="/batchDelete")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "SysUserQuery", name = "query", value = "")
})
@ApiOperation(value = "批量删除【用户信息表】数据", notes = "批量删除【用户信息表】数据", httpMethod = "POST")
public JSONResult batchDelete(@RequestBody SysUserQuery query){
//批量删除数据库数据
sysUserService.removeByIds(Arrays.asList(query.getIds()));
return JSONResult.success(true);
}
/**
* 单个删除【用户信息表】数据
*/
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", dataType = "long", name = "id", value = "")
})
@ApiOperation(value = "单个删除【用户信息表】数据", notes = "单个删除【用户信息表】数据", httpMethod = "DELETE")
@DeleteMapping("/singleDelete/{id}")
public JSONResult batchDelete(@PathVariable("id") Long id){
//单个删除数据库数据
sysUserService.removeById(id);
return JSONResult.success(true);
}
/**
* 根据ID查询【用户信息表】详情数据
*/
@GetMapping(value = "/{id}")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", dataType = "long", name = "id", value = "")
})
@ApiOperation(value = "根据ID查询【用户信息表】详情数据", notes = "根据ID查询【用户信息表】详情数据", httpMethod = "GET")
public JSONResult get(@PathVariable("id")Long id){
return JSONResult.success(sysUserService.getById(id));
}
/**
* 查询【用户信息表】所有数据(不分页)
*/
@GetMapping(value = "/list")
@ApiOperation(value = "查询【用户信息表】所有数据(不分页)", notes = "查询【用户信息表】所有数据(不分页)", httpMethod = "GET")
public JSONResult list(){
List<SysUser> list = sysUserService.list(null);
return JSONResult.success(list);
}
/**
* 查询【用户信息表】数据(分页)
* @param query 查询对象
* @return PageList 分页对象
*/
@PostMapping(value = "/pagelist")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "SysUserQuery", name = "query", value = "查询对象")
})
@ApiOperation(value = "查询【用户信息表】数据(分页)", notes = "查询【用户信息表】数据(分页)", httpMethod = "POST")
public JSONResult pagelist(@RequestBody SysUserQuery query){
IPage<SysUser> page = sysUserService.selectMyPage(query);
return JSONResult.success(new PageList<>(page.getTotal(), page.getRecords()));
}
/**
* 获取用户个人信息
* @param token
* @return
*/
@GetMapping("/info")
public JSONResult getUSerInfo(@RequestParam("token") String token){
return sysUserService.getUSerInfo(token);
}
}

View File

@@ -0,0 +1,59 @@
package cn.rensijin.cchs.domain.app;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableField;
import cn.rensijin.cchs.domain.base.BaseDomain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
* 意见反馈表
* </p>
*
* @date 2025-02-12
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="app_suggestion 表对应的实体对象", description="意见反馈表")
public class AppSuggestion extends BaseDomain implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "提意见时间")
@TableField(value = "create_time")
private LocalDateTime createTime;
@ApiModelProperty(value = "提意见人ID")
@TableField(value = "user_id")
private Long appUserId;
@ApiModelProperty(value = "意见内容")
@TableField(value = "suggestion_countent")
private String suggestionCountent;
@ApiModelProperty(value = "投诉事由")
@TableField(value = "complaint")
private String complaint;
@ApiModelProperty(value = "解决诉求")
@TableField(value = "demands")
private String demands;
@ApiModelProperty(value = "投诉状态")
@TableField(value = "status")
private String status;
}

View File

@@ -0,0 +1,22 @@
package cn.rensijin.cchs.domain.base;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @desc 基础Domain类
*/
@Data
public class BaseDomain implements Serializable {
@ApiModelProperty(value = "创建人名称")
@TableField(exist = false)
private String createUserName;
@ApiModelProperty(value = "更新人名称")
@TableField(exist = false)
private String updateUserName;
}

View File

@@ -0,0 +1,85 @@
package cn.rensijin.cchs.domain.system;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableField;
import cn.rensijin.cchs.domain.base.BaseDomain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
* 用户信息表
* </p>
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="sys_user 表对应的实体对象", description="用户信息表")
public class SysUser extends BaseDomain implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "用户账号")
@TableField(value = "user_name")
private String username;
@ApiModelProperty(value = "用户账号")
@TableField(value = "real_name")
private String realName;
@ApiModelProperty(value = "用户邮箱")
@TableField(value = "email")
private String email;
@ApiModelProperty(value = "手机号码")
@TableField(value = "phonenumber")
private String phonenumber;
@ApiModelProperty(value = "头像地址")
@TableField(value = "avatar")
private String avatar;
@ApiModelProperty(value = "密码")
@TableField(value = "password")
private String password;
@ApiModelProperty(value = "备注")
@TableField(value = "remark")
private String remark;
@ApiModelProperty(value = "创建时间")
@TableField(value = "create_time")
private LocalDateTime createTime;
@ApiModelProperty(value = "创建人")
@TableField(value = "create_user_id")
private Long createUserId;
@ApiModelProperty(value = "更新时间")
@TableField(value = "update_time")
private LocalDateTime updateTime;
@ApiModelProperty(value = "更新人")
@TableField(value = "update_user_id")
private Long updateUserId;
@ApiModelProperty(value = "UUID")
@TableField(exist = false)
private String uuid;
@ApiModelProperty(value = "图片验证码内容")
@TableField(exist = false)
private String imageCode;
}

View File

@@ -0,0 +1,25 @@
package cn.rensijin.cchs.dto;
import java.time.LocalDateTime;
public class AppSuggestionVO {
private Long id;
private LocalDateTime createTime;
private Long userId;
private String suggestionCountent;
private String complaint;
private String demands;
private String status;
private String realName;
}

View File

@@ -0,0 +1,16 @@
package cn.rensijin.cchs.dto;
import lombok.Data;
@Data
public class UserToken {
/**
* 用户ID
*/
private Long id;
/**
* 用户真实姓名
*/
private String nickName;
}

View File

@@ -0,0 +1,43 @@
package cn.rensijin.cchs.exception;
import cn.rensijin.cchs.constants.ErrorCode;
import cn.rensijin.cchs.result.JSONResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理类
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 自定义异常
*/
@ExceptionHandler(MyException.class)
public JSONResult MyException(MyException e){
log.error(e.getLocalizedMessage());
return JSONResult.error(e.getCode(), e.getMessage());
}
/**
* 算术异常
*/
@ExceptionHandler(ArithmeticException.class)
public JSONResult methodArithmeticException(ArithmeticException e){
log.error(e.getLocalizedMessage());
return JSONResult.error("发生算术异常,请稍后再试!");
}
/**
* 如果发生上面没有捕获的异常,那么统一走这个异常捕获,相当于是最大的一个范围
*/
@ExceptionHandler(Exception.class)
public JSONResult exceptionHandler(Exception e){
e.printStackTrace();
log.error(e.getLocalizedMessage());
return JSONResult.error(ErrorCode.SYSTEM_ERROR.getMessage());
}
}

View File

@@ -0,0 +1,23 @@
package cn.rensijin.cchs.exception;
import cn.rensijin.cchs.constants.ErrorCode;
import lombok.Data;
/**
* 自定义异常类
*/
@Data
public class MyException extends RuntimeException {
private String code;
private String message;
public MyException(String message){
this.message = message;
}
public MyException(ErrorCode errorCode){
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
}

View File

@@ -0,0 +1,78 @@
package cn.rensijin.cchs.interceptor;
import cn.rensijin.cchs.annotation.IgnoreAuth;
import cn.rensijin.cchs.constants.ErrorCode;
import cn.rensijin.cchs.constants.SystemConstants;
import cn.rensijin.cchs.exception.MyException;
import cn.rensijin.cchs.util.MyTools;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* 校验登录的拦截器
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate redisTemplate;
//前置拦截
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 放行静态资源请求
if (isStaticResourceRequest(request)) {
return true;
}
if (!(handler instanceof HandlerMethod)) {
//不是请求方法,则直接放行
return true;
}
IgnoreAuth annotation = ((HandlerMethod) handler).getMethodAnnotation(IgnoreAuth.class);
if (annotation != null) {
//用IgnoreAuth注解标识的接口直接放行
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String methodName = method.getName();
System.out.println("methodName=" + methodName);
//1、从请求头中获取token
String token = request.getHeader("token");
if (!MyTools.hasLength(token)) {
throw new MyException(ErrorCode.ERROR_CODE_1002.getMessage());
}
//根据token从Redis中获取到用户信息
String userInfo = (String) redisTemplate.opsForValue().get(SystemConstants.LOGIN_TOKEN_KEY_APP + token);
if (MyTools.hasLength(userInfo)) {
//重新设置超时时间
redisTemplate.opsForValue().set(SystemConstants.LOGIN_TOKEN_KEY_APP + token, userInfo, SystemConstants.LOGIN_TIME_OUT, TimeUnit.MINUTES);
//放行
return true;
} else {
//Redis中拿不到用户信息抛异常
throw new MyException(ErrorCode.ERROR_CODE_1002.getMessage());
}
}
private boolean isStaticResourceRequest(HttpServletRequest request) {
String uri = request.getRequestURI();
// 根据你的静态资源路径配置来判断
return uri.startsWith("/static/") || uri.startsWith("/images/") || uri.startsWith("/Resource/");
}
}

View File

@@ -0,0 +1,90 @@
package cn.rensijin.cchs.interceptor;
import cn.rensijin.cchs.annotation.RateLimit;
import cn.rensijin.cchs.exception.MyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
/**
* <p>防刷限流的拦截器</p>
*/
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, Integer> redisTemplate;
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 如果请求的是方法,则需要做校验
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取目标方法上是否有指定注解
RateLimit rateLimit = handlerMethod.getMethodAnnotation(RateLimit.class);
if (rateLimit == null) {
//说明目标方法上没有 RateLimit 注解
return true;
}
//代码执行到此,说明目标方法上有 RateLimit 注解,所以需要校验这个请求是不是在刷接口
// 获取请求IP地址 192.168.12.13
String ip = getIpAddr(request);
// 请求url路径 /test/test001
String uri = request.getRequestURI();
//存到redis中的key
String key = rateLimit.key() + ip + ":" + uri;//testLimit:192.168.12.13:/test/test001
// 缓存中存在key在限定访问周期内已经调用过当前接口
//2秒内发了10次请求
if (redisTemplate.hasKey(key)) {
// 访问次数自增1
redisTemplate.opsForValue().increment(key, 1);
// 超出访问次数限制
if (redisTemplate.opsForValue().get(key) > rateLimit.count()) {
throw new MyException(rateLimit.msg());
}
// 未超出访问次数限制不进行任何操作返回true
} else {
// 第一次设置数据,过期时间为注解确定的访问周期
redisTemplate.opsForValue().set(key, 1, rateLimit.cycle(), TimeUnit.SECONDS);
}
return true;
}
//如果请求的不是方法,直接放行
return true;
}
//获取请求的归属IP地址
private String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
}
// 对于通过多个代理的情况第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) {
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
}

View File

@@ -0,0 +1,30 @@
package cn.rensijin.cchs.mapper.app;
import cn.rensijin.cchs.domain.app.AppSuggestion;
import cn.rensijin.cchs.dto.AppSuggestionVO;
import cn.rensijin.cchs.query.app.AppSuggestionQuery;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* <p>
* 意见反馈表 Mapper接口
* </p>
*/
public interface AppSuggestionMapper extends BaseMapper<AppSuggestion> {
//查询分页列表数据
List<AppSuggestion> selectMySqlPage(Page<AppSuggestion> page, @Param("query") AppSuggestionQuery query);
@Select("SELECT a.*, s.real_name as realName" +
"FROM app_suggestion a " +
"LEFT JOIN sys_user s ON a.user_id = s.id " +
"ORDER BY a.id DESC")
List<AppSuggestionVO> selectWithUserInfo();
}

View File

@@ -0,0 +1,12 @@
package cn.rensijin.cchs.mapper.system;
import cn.rensijin.cchs.domain.system.SysUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 用户信息表 Mapper接口
* </p>
*/
public interface SysUserMapper extends BaseMapper<SysUser> {
}

View File

@@ -0,0 +1,9 @@
package cn.rensijin.cchs.query.app;
import cn.rensijin.cchs.query.base.BaseQuery;
/**
* 意见反馈表-查询对象
*/
public class AppSuggestionQuery extends BaseQuery{
}

View File

@@ -0,0 +1,17 @@
package cn.rensijin.cchs.query.base;
import lombok.Data;
import java.io.Serializable;
/**
* 基础查询对象
*/
@Data
public class BaseQuery implements Serializable {
private Long[] ids; //批量删除时前端传来的主键ID集合
private String keyword;//关键字
private Integer current = 1; //当前页
private Integer size = 10; //每页显示多少条
}

View File

@@ -0,0 +1,9 @@
package cn.rensijin.cchs.query.system;
import cn.rensijin.cchs.query.base.BaseQuery;
/**
* @desc 用户信息表-查询对象
*/
public class SysUserQuery extends BaseQuery{
}

View File

@@ -0,0 +1,124 @@
package cn.rensijin.cchs.result;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 统一返回结果对象
*/
@Data
@ApiModel(value = "响应数据封装类")
public class JSONResult implements Serializable {
@ApiModelProperty(value = "响应状态描述true:成功;false:失败;")
private boolean success = true;
@ApiModelProperty(value = "响应描述")
private String message = "操作成功";
//成功统一返回0000其余编码全部是错误码
@ApiModelProperty(value = "响应状态码")
private String code = "0000";
//是否展示提示信息(默认不展示)
@ApiModelProperty(value = "是否展示提示信息")
private boolean showMessage = false;
//返回的数据
@ApiModelProperty(value = "响应数据")
private Object data;
//创建当前实例
public static JSONResult success(){
return new JSONResult();
}
//创建当前实例
public static JSONResult success(boolean showMessage){
JSONResult instance = new JSONResult();
instance.setShowMessage(showMessage);
return instance;
}
//创建当前实例
public static JSONResult success(Object obj){
JSONResult instance = new JSONResult();
instance.setData(obj);
return instance;
}
//创建当前实例
public static JSONResult success(Object obj, boolean showMessage){
JSONResult instance = new JSONResult();
instance.setData(obj);
instance.setShowMessage(showMessage);
return instance;
}
//创建当前实例
public static JSONResult successMessage(boolean showMessage, String message){
JSONResult instance = new JSONResult();
instance.setShowMessage(showMessage);
instance.setMessage(message);
return instance;
}
//成功,但是返回不同消息代码
public static JSONResult success(Object obj, String code){
JSONResult instance = new JSONResult();
instance.setSuccess(true);
instance.setCode(code);
instance.setData(obj);
return instance;
}
public static JSONResult success(String code, String message){
JSONResult instance = new JSONResult();
instance.setSuccess(true);
instance.setCode(code);
instance.setMessage(message);
return instance;
}
//创建当前实例
public static JSONResult error(){
JSONResult instance = new JSONResult();
instance.setCode("9999");
instance.setSuccess(false);
instance.setMessage("系统发生异常,请稍后再试!");
return instance;
}
//创建当前实例
public static JSONResult error(String message){
JSONResult instance = new JSONResult();
instance.setCode("9999");
instance.setSuccess(false);
instance.setMessage(message);
return instance;
}
public static JSONResult error(String message, Object obj){
JSONResult instance = new JSONResult();
instance.setCode("9999");
instance.setMessage(message);
instance.setSuccess(false);
instance.setData(obj);
return instance;
}
public static boolean hasLength(String str) {
return org.springframework.util.StringUtils.hasLength(str);
}
public static JSONResult error(String code, String message){
JSONResult instance = new JSONResult();
instance.setCode(hasLength(code) ? code : "9999");
instance.setMessage(message);
instance.setSuccess(false);
return instance;
}
}

View File

@@ -0,0 +1,33 @@
package cn.rensijin.cchs.result;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 分页对象
*/
@Data
public class PageList<T> {
private long total;
private List<T> rows = new ArrayList<>();
@Override
public String toString() {
return "PageList{" +
"total=" + total +
", rows=" + rows +
'}';
}
//提供有参构造方法,方便测试
public PageList(long total, List<T> rows) {
this.total = total;
this.rows = rows;
}
//除了有参构造方法,还需要提供一个无参构造方法
public PageList() {
}
}

View File

@@ -0,0 +1,55 @@
package cn.rensijin.cchs.service.app;
import cn.rensijin.cchs.domain.app.AppSuggestion;
import cn.rensijin.cchs.dto.AppSuggestionVO;
import cn.rensijin.cchs.mapper.app.AppSuggestionMapper;
import cn.rensijin.cchs.query.app.AppSuggestionQuery;
import cn.rensijin.cchs.util.MyTools;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import java.util.List;
/**
* <p>
* 意见反馈表 服务实现类
* </p>
*/
@Transactional
@Service
@Slf4j
public class AppSuggestionService extends ServiceImpl<AppSuggestionMapper, AppSuggestion>{
@Resource
private AppSuggestionMapper appsuggestionMapper;
//查询分页列表数据(使用QueryWrapper操作)
public Page<AppSuggestion> selectMyPage(AppSuggestionQuery query) {
QueryWrapper<AppSuggestion> wrapper = new QueryWrapper<>();
if (MyTools.hasLength(query.getKeyword())) {
wrapper.and(i -> i.like("id", query.getKeyword()));
}
//排序
wrapper.orderByDesc("id");
Page<AppSuggestion> page = new Page<>(query.getCurrent(), query.getSize());
return super.page(page, wrapper);
}
//查询分页列表数据(自己写SQL)
public Page<AppSuggestion> selectMySqlPage(AppSuggestionQuery query) {
Page<AppSuggestion> page = new Page<>(query.getCurrent(), query.getSize());
List<AppSuggestion> list = appsuggestionMapper.selectMySqlPage(page, query);
return page.setRecords(list);
}
public List<AppSuggestionVO> selectWithUserInfo(){
return appsuggestionMapper.selectWithUserInfo();
}
}

View File

@@ -0,0 +1,17 @@
package cn.rensijin.cchs.service.system;
import cn.rensijin.cchs.vo.UploadFileVo;
import org.springframework.web.multipart.MultipartFile;
/**
* <p>文件上传接口</p>
*/
public interface FileService {
/**
* 单个文件上传方法
* @param multipartFile 文件
* @param folderName 保存文件夹名称
*/
UploadFileVo uploadFile(MultipartFile multipartFile, String folderName);
}

View File

@@ -0,0 +1,67 @@
package cn.rensijin.cchs.service.system;
import cn.rensijin.cchs.constants.ErrorCode;
import cn.rensijin.cchs.constants.SystemConstants;
import cn.rensijin.cchs.domain.system.SysUser;
import cn.rensijin.cchs.exception.MyException;
import cn.rensijin.cchs.mapper.system.SysUserMapper;
import cn.rensijin.cchs.query.system.SysUserQuery;
import cn.rensijin.cchs.result.JSONResult;
import cn.rensijin.cchs.util.MyTools;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 用户信息表 服务实现类
* </p>
*/
@Service
@Slf4j
@Transactional
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
@Resource
private RedisTemplate redisTemplate;
//查询分页列表数据
public IPage<SysUser> selectMyPage(SysUserQuery query) {
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
if (MyTools.hasLength(query.getKeyword())) {
wrapper.and(i -> i.like("user_name", query.getKeyword()));
}
//排序
wrapper.orderByDesc("create_time").orderByDesc("id");
IPage<SysUser> page = new Page<>(query.getCurrent(), query.getSize());
return super.page(page, wrapper);
}
//获取用户个人信息
public JSONResult getUSerInfo(String token) {
if (StringUtils.hasLength(token)) {
Object userInfo = redisTemplate.opsForValue().get(SystemConstants.LOGIN_TOKEN_KEY_APP + token);
if (userInfo == null) {
throw new MyException(ErrorCode.ERROR_CODE_1002.getMessage());
}
SysUser sysUser = JSONObject.parseObject(userInfo.toString(), SysUser.class);
Map<String, String> map = new HashMap<>();
map.put("name", sysUser.getUsername());
map.put("avatar", sysUser.getAvatar());
return JSONResult.success(map);
} else {
throw new MyException(ErrorCode.ERROR_CODE_1008.getMessage());
}
}
}

View File

@@ -0,0 +1,58 @@
package cn.rensijin.cchs.service.system.impl;
import cn.rensijin.cchs.exception.MyException;
import cn.rensijin.cchs.service.system.FileService;
import cn.rensijin.cchs.util.DateUtils;
import cn.rensijin.cchs.vo.UploadFileVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.Date;
/**
* <p>文件上传-保存到服务器本地磁盘</p>
*/
@Service
@Slf4j
public class FileServiceLocalImpl implements FileService {
@Value("${server.port}")
private String port;
@Override
public UploadFileVo uploadFile(MultipartFile file, String folderName) {
if (file.isEmpty()) {
throw new MyException("上传文件不能为空!");
}
UploadFileVo vo = new UploadFileVo();
try {
String fileExt = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
File path = new File(ResourceUtils.getURL("classpath:").getPath());
if (!path.exists()) {
path = new File("");
}
String localPath = "/UploadFiles/" + DateUtils.getCurrentYearMonth() + "/" + folderName + "/";
File upload = new File(path.getAbsolutePath(), localPath);
if (!upload.exists()) {
upload.mkdirs();
}
String fileName = new Date().getTime() + "." + fileExt;
File dest = new File(upload.getAbsolutePath() + "/" + fileName);
file.transferTo(dest);
//如果使用idea或者eclipse重启项目发现之前上传的图片或者文件丢失将下面一行代码注释打开
FileUtils.copyFile(dest, new File("E:\\workspace\\EasyJavaTemplateMaster\\EasyJavaTemplate\\SpringBoot\\src\\main\\resources\\static\\UploadFiles" + "/" + DateUtils.getCurrentYearMonth() + "/" + folderName + "/" + fileName));
//封装返回数据
vo.setFileName(fileName);
vo.setFileSize(file.getSize());
vo.setFileFullPath("http://localhost:" + port + "/static/UploadFiles/" + DateUtils.getCurrentYearMonth() + "/" + folderName + "/" + fileName);
} catch (Exception e) {
e.printStackTrace();
throw new MyException("上传文件到本地磁盘异常:" + e.getMessage());
}
return vo;
}
}

View File

@@ -0,0 +1,197 @@
package cn.rensijin.cchs.util;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 时间工具类
*/
public class DateUtils extends org.apache.commons.lang3.time.DateUtils
{
public static String YYYY = "yyyy";
public static String YYYYMM = "yyyyMM";
public static String MM = "MM";
public static String YYYY_MM = "yyyy-MM";
public static String YYYY_MM_DD = "yyyy-MM-dd";
public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
private static String[] parsePatterns = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
public static void main(String[] args) {
System.out.println(getCurrentDateString());
}
/**
* 获取当前年月
*/
public static String getCurrentYearMonth()
{
return dateTimeNow(YYYYMM);
}
/**
* 获取当前月份
*/
public static String getMonth()
{
return dateTimeNow(MM);
}
/**
* 获取当前Date型日期
*
* @return Date() 当前日期
*/
public static Date getNowDate()
{
return new Date();
}
/**
* 获取当前日期, 默认格式为yyyy-MM-dd
*/
public static String getDate()
{
return dateTimeNow(YYYY_MM_DD);
}
public static String getYear()
{
return dateTimeNow(YYYY);
}
public static final String getTime()
{
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
}
public static final String dateTimeNow()
{
return dateTimeNow(YYYYMMDDHHMMSS);
}
public static final String dateTimeNow(final String format)
{
return parseDateToStr(format, new Date());
}
public static final String dateTime(final Date date)
{
return parseDateToStr(YYYY_MM_DD, date);
}
public static final String parseDateToStr(final String format, final Date date)
{
return new SimpleDateFormat(format).format(date);
}
public static final String getCurrentDateString()
{
return parseDateToStr(YYYY_MM_DD_HH_MM_SS, new Date());
}
public static final String getCurrentDateString(String dateExpression)
{
return parseDateToStr(dateExpression, new Date());
}
public static final LocalDateTime getCurrentLocalDateTime()
{
return LocalDateTime.now();
}
public static final Date dateTime(final String format, final String ts)
{
try
{
return new SimpleDateFormat(format).parse(ts);
}
catch (ParseException e)
{
throw new RuntimeException(e);
}
}
/**
* 日期路径 即年/月/日 如2018/08/08
*/
public static final String datePath()
{
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}
/**
* 日期路径 即年/月/日 如20180808
*/
public static final String dateTime()
{
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}
/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object str)
{
if (str == null)
{
return null;
}
try
{
return parseDate(str.toString(), parsePatterns);
}
catch (ParseException e)
{
return null;
}
}
/**
* 获取服务器启动时间
*/
public static Date getServerStartDate()
{
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
/**
* 计算两个时间差
*/
public static String getDatePoor(Date endDate, Date nowDate)
{
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "" + hour + "小时" + min + "分钟";
}
}

View File

@@ -0,0 +1,57 @@
package cn.rensijin.cchs.util;
import cn.hutool.core.lang.UUID;
import org.apache.commons.lang3.StringUtils;
/**
* ID生成器工具类
*/
public class IdUtils {
/**
* 功能说明 获取随机UUID数去掉了 -
*/
public static String getSimpleUUID() {
return StringUtils.replace(java.util.UUID.randomUUID().toString(), "-", "");
}
/**
* 获取随机UUID
*
* @return 随机UUID
*/
public static String randomUUID()
{
return UUID.randomUUID().toString();
}
/**
* 简化的UUID去掉了横线
*
* @return 简化的UUID去掉了横线
*/
public static String simpleUUID()
{
return UUID.randomUUID().toString(true);
}
/**
* 获取随机UUID使用性能更好的ThreadLocalRandom生成UUID
*
* @return 随机UUID
*/
public static String fastUUID()
{
return UUID.fastUUID().toString();
}
/**
* 简化的UUID去掉了横线使用性能更好的ThreadLocalRandom生成UUID
*
* @return 简化的UUID去掉了横线
*/
public static String fastSimpleUUID()
{
return UUID.fastUUID().toString(true);
}
}

View File

@@ -0,0 +1,199 @@
package cn.rensijin.cchs.util;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.crypto.SecureUtil;
import cn.rensijin.cchs.constants.SystemConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @desc 工具类
*/
@Slf4j
public class MyTools {
public static void main(String[] args){
System.out.println(SecureUtil.md5("123456"));
}
/**
* 获取文件的完整路径
*/
public static String getFileUploadPath(String fileFullName) {
String uploadPath = System.getProperty("user.dir");
return uploadPath + SystemConstants.FILES_DIR + fileFullName;
}
/**
* @Description: 获取随机用户昵称
* 返回长度20
*/
public synchronized static String getNickName() {
return getRandomChar(12).toLowerCase();
}
/**
* @desc 判断字符串是否有长度
*/
public static boolean hasLength(String str) {
return org.springframework.util.StringUtils.hasLength(str);
}
/**
* @desc 获取token值
*/
public static String getLoginToken() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* @Description: 将本地图片转成Base64数据
*/
public static String ImageToBase64(String imgPath) {
byte[] data = null;
// 读取图片字节数组
try {
InputStream in = new FileInputStream(imgPath);
data = new byte[in.available()];
in.read(data);
in.close();
} catch (IOException e) {
log.error("图片转Base64数据时发生异常:{}", e.getLocalizedMessage());
}
// 对字节数组Base64编码
Base64Encoder encoder = new Base64Encoder();
// 返回Base64编码过的字节数组字符串
return "data:img/jpg;base64," + encoder.encode(Objects.requireNonNull(data));
}
/*
* @Description: 验证密码必须是6-18位长度必须由数字和字母组成并且要同时含有数字和字母且长度要在6-18位之间
*/
public static boolean checkPassword(String str){
boolean checkResult = false;
if(!StringUtils.isBlank(str)){
String regEx = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(str);
if(m.matches()){
checkResult = true;
}
}
return checkResult;
}
/*
* @Description: 验证字母和数字必须是6-18位长度
*/
public static boolean checkNumChar(String str){
boolean checkResult = false;
if(!StringUtils.isBlank(str)){
String regEx = "^[0-9A-Za-z]{6,18}$";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(str);
if(m.matches()){
checkResult = true;
}
}
return checkResult;
}
/*
* @Description: 验证手机号
*/
public static boolean checkPhone(String phone){
boolean checkResult = false;
if(!StringUtils.isBlank(phone)){
if(phone.length() == 11){
String regEx = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17[013678])|(18[0, 5-9]))\\d{8}$";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(phone);
if(m.matches()){
checkResult = true;
}
}
}
return checkResult;
}
/*
* @Description: 验证邮箱格式
*/
public static boolean checkEmail(String email){
boolean checkResult = false;
if(!StringUtils.isBlank(email)){
String regEx = "([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(email);
if(m.matches()){
checkResult = true;
}
}
return checkResult;
}
/**
* @Description: 获取指定位数的随机整数
*/
public synchronized static String getRandomNum(int num) {
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 1; i <= num; i++) {
sb.append(random.nextInt(9));
}
return sb.toString();
}
/**
* @description: 获取指定串中随机指定位数的字符串
*/
public synchronized static String getRandomChar(int num) {
//先定义取值范围
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuffer value = new StringBuffer();
for (int i = 0; i < num; i++) {
value.append(chars.charAt((int)(Math.random() * chars.length())));
}
return value.toString();
}
/*
* @Description: 获取当前时间字符串(yyyyMMddHHmmss)
*/
public synchronized static String getCurrentTimeStr() {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
return dateFormat.format(date);
}
/*
* @Description: 根据MultipartFile文件获取图片Base64数据
*/
public static String MutipartFileToBase64(MultipartFile file) {
String base64EncoderImg = "";
try {
Base64Encoder encoder = new Base64Encoder();
base64EncoderImg = "data:img/jpg;base64," + encoder.encode(file.getBytes());
} catch (Exception e) {
log.error("根据MultipartFile文件获取Base64数据异常{}", e);
}
return base64EncoderImg;
}
//随机获取指定区间的整数
public synchronized static String getRandomDotString(int min, int max) {
Random rand = new Random();
StringBuffer result = new StringBuffer();
result.append(rand.nextInt(max - min + 1) + min);
return result.toString();
}
}

View File

@@ -0,0 +1,251 @@
package cn.rensijin.cchs.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Redis 工具类
*/
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 清空Redis所有缓存数据
*/
public void clearAllRedisData()
{
Set<String> keys = redisTemplate.keys("*");
redisTemplate.delete(keys);
}
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
if(timeout == -1){
redisTemplate.opsForValue().set(key, value);
}else{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
if(redisTemplate.hasKey(key)){
return redisTemplate.delete(key);
}
return true;
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
/**
* @desc 判断Redis中是否存在指定的key
*/
public boolean exists(String key) {
return redisTemplate.hasKey(key);
}
}

View File

@@ -0,0 +1,14 @@
package cn.rensijin.cchs.vo;
import lombok.Data;
/**
* <p>登录成功后,返回前端的对象</p>
*/
@Data
public class LoginSuccessVo {
private String token;//token
private String refToken;//刷新token
private Long userId;//用户ID
private Integer roleId;
}

View File

@@ -0,0 +1,15 @@
package cn.rensijin.cchs.vo;
import lombok.Data;
/**
* <p>文件上传成功后返回前端参数</p>
*/
@Data
public class UploadFileVo {
private String fileFullPath;//文件全路径
private String fileName;//文件名称
private String filePrefix;//文件存储路径的前缀
private Long fileSize;//文件大小
}

View File

@@ -0,0 +1,15 @@
package cn.rensijin.cchs.vo;
import lombok.Data;
/**
* @desc 登录成功之后,返回前端的对象
*/
@Data
public class UserTokenVo {
private String token;
private String userName;
private String realName;
private String avatar;
}

View File

@@ -0,0 +1,70 @@
server:
#启动端口
port: 8002
tomcat:
#设置字符编码
uri-encoding: UTF-8
spring:
application:
#系统服务名
name: EasyJavaTemplate
#MySQL数据库相关配置信息
datasource:
url: jdbc:mysql://192.168.100.201:3306/rensijin-cchs?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root #数据库账号
password: Abc123654 #数据库密码
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Redis 缓存相关参数配置
redis:
host: 127.0.0.1
port: 6379 #端口
timeout: 5000 #连接超时 毫秒
password: Redis11 #密码
jedis:
pool:
maxActive: 30 #给定时间可以分配的最大连接数。 使用负值表示没有限制
maxIdle: 30 #最大空闲连接数
minIdle: 10 # 最小空间连接数
maxWait: -1 #连接池最大等待时间 -1没有限制
main:
allow-bean-definition-overriding: true
servlet:
multipart:
#单个文件上传大小限制
max-file-size: 30MB
#单次上传文件总大小限制
max-request-size: 100MB
#MyBatis-Plus相关配置
mybatis-plus:
#指定Mapper.xml路径如果与Mapper路径相同的话可省略
mapper-locations: classpath:cn/wujiangbo/mapper/*Mapper.xml
configuration:
#开启驼峰大小写自动转换
map-underscore-to-camel-case: true
#开启控制台sql输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
easyjava:
#版本号
version: 2.0.5
#系统类型标识
systemType: 'xxx'
#用户注册默认头像地址
userDefaultAvatarPath: https://img2.baidu.com/it/u=1016407180,2828407920&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=800
#OSS相关配置
file:
#文件存储方案(1文件本地2存阿里云OSS)
filescheme: 1
download:
#文件下载用到的服务器IP地址
ip: localhost
alicloud:
enable: true #是否开启OSS上传
bucket-name: xxx #上传空间bucket
access-key: xxx #你的key
secret-key: xxx #你的秘钥
endpoint: oss-cn-beijing.aliyuncs.com #上传端点

View File

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.rensijin.cchs.mapper.app.AppSuggestionMapper">
<select id="selectMySqlPage" resultType="cn.rensijin.cchs.domain.app.AppSuggestion">
select t1.*, t2.real_name as createUserName
from app_suggestion t1
left join sys_user t2 on t1.user_id = t2.id
<where>
<if test="query.keyword != null and query.keyword != '' ">
and t1.complaint like concat('%', #{query.keyword}, '%')
</if>
</where>
order by t1.id desc
</select>
</mapper>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.rensijin.cchs.mapper.system.SysUserMapper">
</mapper>

View File

@@ -0,0 +1,145 @@
<!-- 级别从高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则 根据当前ROOT 级别日志输出时级别高于root默认的级别时 会输出 -->
<!-- 以下 每个配置的 filter 是过滤掉输出文件里面会出现高级别文件依然出现低级别的日志信息通过filter 过滤只记录本级别的日志 -->
<!-- scan 当此属性设置为true时配置文件如果发生改变将会被重新加载默认值为true。 -->
<!-- scanPeriod 设置监测配置文件是否有修改的时间间隔如果没有给出时间单位默认单位是毫秒。当scan为true时此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug 当此属性设置为true时将打印出logback内部日志信息实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 动态日志级别 -->
<jmxConfigurator />
<!-- 定义日志文件 输出位置 -->
<property name="log_dir" value="C:/LOGS/EasyJavaTemplate" />
<!-- 日志最大的历史 30天 -->
<property name="maxHistory" value="30" />
<!-- 单个日志文件的最大大小 -->
<property name="maxFileSize" value="5MB" />
<!--格式化输出:%d表示日期%t表示线程名%-5level级别从左显示5个字符宽度%c:类全路径,%msg日志消息%M:方法名,%L日志所属行号%n换行符Windows平台为"\r\n"Unix平台为"\n" -->
<property name="FORMAT" value="%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c.%M%L%msg%n"/>
<!-- ConsoleAppender 控制台输出日志 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
<!-- 设置日志输出格式 -->
${FORMAT}
</pattern>
</encoder>
</appender>
<!-- ERROR级别日志 -->
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 RollingFileAppender -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>ERROR</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--
滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
%i当文件大小超过maxFileSize时按照i进行文件滚动i的值从0开始递增
-->
<fileNamePattern>
${log_dir}/error/%d{yyyy-MM-dd}/error-%i.log
</fileNamePattern>
<!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件假设设置每个月滚动,且<maxHistory>是6 则只保存最近6个月的文件删除之前的旧文件。注意删除旧文件是那些为了归档而创建的目录也会被删除 -->
<maxHistory>${maxHistory}</maxHistory>
<!-- 日志文件的最大大小 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
<!-- 设置日志输出格式 -->
${FORMAT}
</pattern>
</encoder>
</appender>
<!-- WARN级别日志 appender -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>WARN</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/warn/%d{yyyy-MM-dd}/warn-%i.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<!-- 日志文件的最大大小 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${FORMAT}</pattern>
</encoder>
</appender>
<!-- INFO级别日志 appender -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/info/%d{yyyy-MM-dd}/info-%i.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<!-- 日志文件的最大大小 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${FORMAT}</pattern>
</encoder>
</appender>
<!-- DEBUG级别日志 appender -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/debug/%d{yyyy-MM-dd}/debug-%i.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<!-- 日志文件的最大大小 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${FORMAT}</pattern>
</encoder>
</appender>
<!-- root级别 -->
<root>
<!-- 只有该级别日志及以上级别日志才会输出到指定渠道 -->
<!-- 测试debug。上生产环境时需要改成info或error -->
<level value="info" />
<!-- 控制台输出渠道 -->
<appender-ref ref="console" />
<!-- 文件输出渠道 -->
<appender-ref ref="ERROR" />
<appender-ref ref="INFO" />
<appender-ref ref="WARN" />
<appender-ref ref="DEBUG" />
</root>
</configuration>

View File

@@ -0,0 +1,70 @@
server:
#启动端口
port: 8002
tomcat:
#设置字符编码
uri-encoding: UTF-8
spring:
application:
#系统服务名
name: EasyJavaTemplate
#MySQL数据库相关配置信息
datasource:
url: jdbc:mysql://192.168.100.201:3306/rensijin-cchs?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root #数据库账号
password: Abc123654 #数据库密码
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Redis 缓存相关参数配置
redis:
host: 127.0.0.1
port: 6379 #端口
timeout: 5000 #连接超时 毫秒
password: Redis11 #密码
jedis:
pool:
maxActive: 30 #给定时间可以分配的最大连接数。 使用负值表示没有限制
maxIdle: 30 #最大空闲连接数
minIdle: 10 # 最小空间连接数
maxWait: -1 #连接池最大等待时间 -1没有限制
main:
allow-bean-definition-overriding: true
servlet:
multipart:
#单个文件上传大小限制
max-file-size: 30MB
#单次上传文件总大小限制
max-request-size: 100MB
#MyBatis-Plus相关配置
mybatis-plus:
#指定Mapper.xml路径如果与Mapper路径相同的话可省略
mapper-locations: classpath:cn/wujiangbo/mapper/*Mapper.xml
configuration:
#开启驼峰大小写自动转换
map-underscore-to-camel-case: true
#开启控制台sql输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
easyjava:
#版本号
version: 2.0.5
#系统类型标识
systemType: 'xxx'
#用户注册默认头像地址
userDefaultAvatarPath: https://img2.baidu.com/it/u=1016407180,2828407920&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=800
#OSS相关配置
file:
#文件存储方案(1文件本地2存阿里云OSS)
filescheme: 1
download:
#文件下载用到的服务器IP地址
ip: localhost
alicloud:
enable: true #是否开启OSS上传
bucket-name: xxx #上传空间bucket
access-key: xxx #你的key
secret-key: xxx #你的秘钥
endpoint: oss-cn-beijing.aliyuncs.com #上传端点

View File

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.rensijin.cchs.mapper.app.AppSuggestionMapper">
<select id="selectMySqlPage" resultType="cn.rensijin.cchs.domain.app.AppSuggestion">
select t1.*, t2.real_name as createUserName
from app_suggestion t1
left join sys_user t2 on t1.user_id = t2.id
<where>
<if test="query.keyword != null and query.keyword != '' ">
and t1.complaint like concat('%', #{query.keyword}, '%')
</if>
</where>
order by t1.id desc
</select>
</mapper>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.rensijin.cchs.mapper.system.SysUserMapper">
</mapper>

View File

@@ -0,0 +1,145 @@
<!-- 级别从高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则 根据当前ROOT 级别日志输出时级别高于root默认的级别时 会输出 -->
<!-- 以下 每个配置的 filter 是过滤掉输出文件里面会出现高级别文件依然出现低级别的日志信息通过filter 过滤只记录本级别的日志 -->
<!-- scan 当此属性设置为true时配置文件如果发生改变将会被重新加载默认值为true。 -->
<!-- scanPeriod 设置监测配置文件是否有修改的时间间隔如果没有给出时间单位默认单位是毫秒。当scan为true时此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug 当此属性设置为true时将打印出logback内部日志信息实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 动态日志级别 -->
<jmxConfigurator />
<!-- 定义日志文件 输出位置 -->
<property name="log_dir" value="C:/LOGS/EasyJavaTemplate" />
<!-- 日志最大的历史 30天 -->
<property name="maxHistory" value="30" />
<!-- 单个日志文件的最大大小 -->
<property name="maxFileSize" value="5MB" />
<!--格式化输出:%d表示日期%t表示线程名%-5level级别从左显示5个字符宽度%c:类全路径,%msg日志消息%M:方法名,%L日志所属行号%n换行符Windows平台为"\r\n"Unix平台为"\n" -->
<property name="FORMAT" value="%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c.%M%L%msg%n"/>
<!-- ConsoleAppender 控制台输出日志 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
<!-- 设置日志输出格式 -->
${FORMAT}
</pattern>
</encoder>
</appender>
<!-- ERROR级别日志 -->
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 RollingFileAppender -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>ERROR</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--
滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
%i当文件大小超过maxFileSize时按照i进行文件滚动i的值从0开始递增
-->
<fileNamePattern>
${log_dir}/error/%d{yyyy-MM-dd}/error-%i.log
</fileNamePattern>
<!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件假设设置每个月滚动,且<maxHistory>是6 则只保存最近6个月的文件删除之前的旧文件。注意删除旧文件是那些为了归档而创建的目录也会被删除 -->
<maxHistory>${maxHistory}</maxHistory>
<!-- 日志文件的最大大小 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
<!-- 设置日志输出格式 -->
${FORMAT}
</pattern>
</encoder>
</appender>
<!-- WARN级别日志 appender -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>WARN</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/warn/%d{yyyy-MM-dd}/warn-%i.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<!-- 日志文件的最大大小 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${FORMAT}</pattern>
</encoder>
</appender>
<!-- INFO级别日志 appender -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/info/%d{yyyy-MM-dd}/info-%i.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<!-- 日志文件的最大大小 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${FORMAT}</pattern>
</encoder>
</appender>
<!-- DEBUG级别日志 appender -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/debug/%d{yyyy-MM-dd}/debug-%i.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<!-- 日志文件的最大大小 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${FORMAT}</pattern>
</encoder>
</appender>
<!-- root级别 -->
<root>
<!-- 只有该级别日志及以上级别日志才会输出到指定渠道 -->
<!-- 测试debug。上生产环境时需要改成info或error -->
<level value="info" />
<!-- 控制台输出渠道 -->
<appender-ref ref="console" />
<!-- 文件输出渠道 -->
<appender-ref ref="ERROR" />
<appender-ref ref="INFO" />
<appender-ref ref="WARN" />
<appender-ref ref="DEBUG" />
</root>
</configuration>