SSM整合環境搭建(Axios非同步請求)
SSM整合環境搭建(Axios非同步請求——前後端分離)以員工管理為例
一、maven聚合工程的搭建
1、建立父工程
2、刪除父工程中的src資料夾,右鍵工程名,新建子工程
重複上述步驟建立其他子工程,如若出現pom.xml 變灰 出現刪除線,請參考BUG- IDEA Maven pom.xml 變灰 出現刪除線
3、修改子工程employee_web,將其變成web工程
3.1、選擇employee_web新增web
修改成功後如圖
3.2、更改打包方式為war
二、maven的父工程做版本管理
給相應的子工程新增依賴,以employee_common的pom.xml為例
<?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> <artifactId>employee-parent</View CodeartifactId> <groupId>com.shangma.cn</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>employee_common</artifactId> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <scope>provided</scope> </dependency> </dependencies> </project>
新增依賴時,使用父工程管理依賴的版本
<?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> <groupId>com.shangma.cn</groupId> <artifactId>employee_root</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>employee_common</module> <module>employee_entity</module> <module>employee_mapper</module> <module>employee_service</module> <module>employee_web</module> </modules> <properties> <lombok-version>1.18.12</lombok-version> <mybatis-version>3.5.4</mybatis-version> <mysql-version>5.1.47</mysql-version> <druid-version>1.1.22</druid-version> <spring-version>5.1.9.RELEASE</spring-version> <mybatis-spring-version>2.0.3</mybatis-spring-version> <log4j-version>1.2.17</log4j-version> <aspectjweaver-version>1.9.5</aspectjweaver-version> <jackson-databind-version>2.10.2</jackson-databind-version> <dysmsapi20170525-version>2.0.4</dysmsapi20170525-version> <spring-data-redis-version>2.1.3.RELEASE</spring-data-redis-version> <jedis-version>2.9.1</jedis-version> <slf4j-simple-version>1.7.30</slf4j-simple-version> <pagehelper-version>5.1.10</pagehelper-version> <aliyun-sdk-oss-version>3.10.2</aliyun-sdk-oss-version> </properties> <!-- 版本控制--> <dependencyManagement> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok-version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis-version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis-spring-version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectjweaver-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson-databind-version}</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>${dysmsapi20170525-version}</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>${spring-data-redis-version}</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>${jedis-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>${slf4j-simple-version}</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>${pagehelper-version}</version> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>${aliyun-sdk-oss-version}</version> </dependency> </dependencies> </dependencyManagement> </project>View Code
三、maven的配置類加註解的方式進行SSM整合
在Mapper層和Service層配置Spring,在web層配置Spring MVC和web.xml
1、MpperConfig類(spring)
1.1、載入外部配置檔案(沒寫)
1.2、配置資料來源
1.3、配置SQLSessionFactoryBean
1.4、配置MapperScanConfiguar
package com.shangma.cn.config; import com.alibaba.druid.pool.DruidDataSource; import com.github.pagehelper.PageInterceptor; import org.apache.ibatis.logging.log4j.Log4jImpl; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MapperConfig { /** * 配置資料來源 * @return */ @Bean public DruidDataSource dataSource(){ DruidDataSource dataSource =new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mavenssm"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } /** * 配置SQLSessionFactory,載入mybatis配置檔案 * @return */ @Bean public SqlSessionFactoryBean sessionFactory(){ SqlSessionFactoryBean sessionFactory=new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); return sessionFactory; } /** * 配置mapper掃描 * @return */ @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.shangma.cn.mapper"); return mapperScannerConfigurer; } }View Code
2、ServiceConfig類(spring)
2.1、配置包掃描
2.2、配置事務管理器
2.3、配置事務驅動
package com.shangma.cn.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * @author: JAVASM * @className: ServiceConfig * @description: * @date: 2021/6/14 13:03 * @version: 0.1 * @since: jdk1.8 */ @Configuration @ComponentScan(basePackages = {"com.shangma.cn.service"})//包掃描 @EnableTransactionManagement//開啟事務註解驅動 public class ServiceConfig { @Autowired private DruidDataSource dataSource; /** * 事務管理器 * @return */ @Bean public DataSourceTransactionManager transactionManager(){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }View Code
3、WebConfig類(spring mvc)
3.1、配置包掃描
3.2、配置mvc的註解驅動
package com.shangma.cn.config; import com.shangma.cn.interceptor.LoginInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import redis.clients.jedis.JedisPoolConfig; /** * @author: JAVASM * @className: WebConfig * @description: * @date: 2021/6/14 13:04 * @version: 0.1 * @since: jdk1.8 */ @ComponentScan(basePackages = {"com.shangma.cn.controller", "com.shangma.cn.exception"}) @EnableWebMvc//開啟MVC註解驅動 @Configuration//加入容器 public class WebConfig implements WebMvcConfigurer { }View Code
4、MyWebApplnitalizer類(web.xml)
4.1、載入spring的配置類
4.2、載入spring mvc的配置類
4.3、設定開啟檔案上傳
4.4、設定Filter解決亂碼問題(沒寫)
package com.shangma.cn; import com.shangma.cn.config.MapperConfig; import com.shangma.cn.config.ServiceConfig; import com.shangma.cn.config.WebConfig; import org.springframework.context.annotation.ComponentScan; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.DelegatingFilterProxy; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer; import javax.servlet.Filter; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletRegistration; /** * @author: JAVASM * @className: MyWebAppInitializer * @description: * @date: 2021/6/14 19:16 * @version: 0.1 * @since: jdk1.8 */ public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 載入父容器的配置類 * @return */ @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{MapperConfig.class, ServiceConfig.class}; } /** * 載入子容器的配置類 * @return */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{WebConfig.class}; } /** * 表示設定DispatcherServlet路徑 * @return */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } /** * 檔案上傳 * @param registration */ @Override protected void customizeRegistration(ServletRegistration.Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement("")); } }View Code
四、逆向工程生成和mybatis的日誌列印
使用逆向工程,能夠一鍵生成entity層和mapper層的程式碼,CV大法到相應的位置
因為使用的是配置類的方式進行SSM的整合,所以沒有了配置檔案,因此在配置SQLSessionFactoryBean時並沒有對mybatis的日誌列印進行設定
配置檔案在配置類中都有對應的寫法,匯入log4j的配置檔案,修改後的MpperConfig類如下:
package com.shangma.cn.config; import com.alibaba.druid.pool.DruidDataSource; import com.github.pagehelper.PageInterceptor; import org.apache.ibatis.logging.log4j.Log4jImpl; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author: JAVASM * @className: MapperConfig * @description: * @date: 2021/6/14 13:02 * @version: 0.1 * @since: jdk1.8 */ @Configuration public class MapperConfig { /** * 配置資料來源 * @return */ @Bean public DruidDataSource dataSource(){ DruidDataSource dataSource =new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mavenssm"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } /** * 配置SQLSessionFactory,載入mybatis配置檔案 * @return */ @Bean public SqlSessionFactoryBean sessionFactory(){ SqlSessionFactoryBean sessionFactory=new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); //設定mybatis的日誌 org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); configuration.setLogImpl(Log4jImpl.class); sessionFactory.setConfiguration(configuration); return sessionFactory; } /** * 配置mapper掃描 * @return */ @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.shangma.cn.mapper"); return mapperScannerConfigurer; } }View Code
五、傳送手機驗證碼
package com.shangma.cn.controller; import com.shangma.cn.common.AxiosResult; import com.shangma.cn.common.AxiosStatus; import com.shangma.cn.entity.Employee; import com.shangma.cn.exception.MyLoginException; import com.shangma.cn.factory.AsyncFactory; import com.shangma.cn.pool.AsyncManager; import com.shangma.cn.service.EmployeeService; import com.shangma.cn.utils.RandomCodeUtil; import com.shangma.cn.utils.SmsUtil; import com.shangma.cn.utils.UploadUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; import javax.servlet.http.Part; import java.io.IOException; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @author: JAVASM * @className: CommonController * @description: * @date: 2021/6/14 19:15 * @version: 0.1 * @since: jdk1.8 */ @RestController @RequestMapping("common") public class CommonController { @Autowired private EmployeeService employeeService; @RequestMapping("getCode/{phone}") public String getCode(@PathVariable String phone){ System.out.println(phone); Employee employee = employeeService.findByPhone(phone); System.out.println(employee); if (employee==null) { return "NOT_Employee"; } //生成驗證碼 String code = RandomCodeUtil.munCode(); //傳送簡訊,單獨的Java不能實現傳送手機驗證碼 //所以需要依賴於第三方的SDK,本案例使用的是阿里雲所提供的服務 SmsUtil.sendSms(phone,code); return "success"; } }View Code
六、使用非同步執行緒池傳送手機驗證碼
傳送簡訊是一個很耗時間的操作,非常容易造成執行緒的阻塞,所以我們可以把傳送這個操作發到子執行緒中,並且傳送驗證碼這個操作過程複雜
需要 一定的程式碼量,而且耦合度高、使用的次數多,所以我們可以使用工廠模式降低程式碼的重複和解耦。
還有就是返回給前端的值,一般都會返回json形式的字串,並且在前端用於條件判斷的多是數字(狀態碼),因此,我們先設定好返回值類,定義好返回值(列舉類)
定義狀態碼
View Code定義返回值類
package com.shangma.cn.common; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; @Data public class AxiosResult<T> { private int status; private String message; private T data; /** * 私有化建構函式 */ private AxiosResult() { } /** * 返回狀態碼不帶資料 * @param <T> * @return */ public static <T> AxiosResult<T> success() { return setData(AxiosStatus.OK, null); } /** * 返回狀態碼帶資料 * @param t * @param <T> * @return */ public static <T> AxiosResult<T> success(T t) { return setData(AxiosStatus.OK, t); } /** * 返回別的型別的狀態碼不帶資料 * @param axiosStatus * @param <T> * @return */ public static <T> AxiosResult<T> success(AxiosStatus axiosStatus) { return setData(axiosStatus, null); } /** * 返回別的型別的狀態碼帶資料 * @param axiosStatus * @param t * @param <T> * @return */ public static <T> AxiosResult<T> success(AxiosStatus axiosStatus, T t) { return setData(axiosStatus, t); } public static <T> AxiosResult<T> error() { return setData(AxiosStatus.ERROR, null); } public static <T> AxiosResult<T> error(AxiosStatus axiosStatus) { return setData(axiosStatus, null); } public static <T> AxiosResult<T> error(AxiosStatus axiosStatus, T t) { return setData(axiosStatus, t); } public static <T> AxiosResult<T> setData(AxiosStatus axiosStatus, T t) { AxiosResult<T> tAxiosResult = new AxiosResult<>(); tAxiosResult.setStatus(axiosStatus.getStatus()); tAxiosResult.setMessage(axiosStatus.getMessage()); tAxiosResult.setData(t); return tAxiosResult; } }View Code
執行緒池
package com.shangma.cn.pool; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 使用單例模式設計非同步執行緒池 */ public class AsyncManager { private static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; private static AsyncManager asyncManager; private AsyncManager (){ scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(50); } /** * 建立單例物件 * @return */ public static AsyncManager getInstance(){ if (asyncManager==null) { return new AsyncManager(); } return asyncManager; } /** * 使用執行緒池執行內容 * 這種執行沒有延遲時間 * * @param runnable */ public void execute(Runnable runnable) { scheduledThreadPoolExecutor.execute(runnable); } /** * 使用執行緒執行程式碼 * schedule方法可以延遲一段時間後再去執行Runnable中的程式碼 * @param runnable 要執行的程式碼 * @param seconds 延遲的時間 */ public void schedule(Runnable runnable,long seconds){ scheduledThreadPoolExecutor.schedule(runnable,seconds, TimeUnit.SECONDS); } /** * 關閉執行緒池 */ public void release(){ scheduledThreadPoolExecutor.shutdownNow(); } }View Code
工廠類
package com.shangma.cn.factory; import com.aliyun.dysmsapi20170525.Client; import com.aliyun.dysmsapi20170525.models.SendSmsRequest; import com.aliyun.teaopenapi.models.Config; import com.shangma.cn.utils.SmsUtil; import lombok.extern.log4j.Log4j; import java.io.IOException; import java.util.Properties; /** * 非同步工廠(使用工廠模式完成功能) * 1、傳送手機驗證碼 */ @Log4j public class AsyncFactory { private static Properties properties; static { //載入配置檔案 try { properties = new Properties(); properties.load(SmsUtil.class.getClassLoader().getResourceAsStream("aliyun.properties")); } catch (IOException e) { e.printStackTrace(); } } /** * * @param phone * @param code * @return */ public static Runnable sendPhoneCode(String phone, String code){ Config config = new Config() // 您的AccessKey ID .setAccessKeyId(properties.getProperty("aliyun.accessKeyId")) // 您的AccessKey Secret .setAccessKeySecret(properties.getProperty("aliyun.accessKeySecret")); // 訪問的域名 config.endpoint = properties.getProperty("aliyun.endpoint"); try { Client client = new Client(config); SendSmsRequest sendSmsRequest = new SendSmsRequest() .setPhoneNumbers(phone) .setSignName(properties.getProperty("aliyun.signName")) .setTemplateCode(properties.getProperty("aliyun.templateCode")) .setTemplateParam("{\"code\":" + code + "}"); // 複製程式碼執行請自行列印 API 的返回值 Runnable runnable = ()->{ //當前執行緒名 System.out.println("當前執行緒為:"+Thread.currentThread().getName()); try { client.sendSms(sendSmsRequest); } catch (Exception e) { e.printStackTrace(); } }; return runnable; } catch (Exception e) { log.error(phone + "+手機驗證碼傳送失敗:" + e.getMessage()); e.printStackTrace(); } return ()->{}; } }View Code
修改後的傳送手機驗證碼
@RequestMapping("getCode/{phone}") public AxiosResult<Void> getCode(@PathVariable String phone){ System.out.println(phone); Employee employee = employeeService.findByPhone(phone); System.out.println(employee); if (employee==null) { return AxiosResult.error(); } //生成驗證碼 String code = String.valueOf((int) (Math.random() * (999999 - 100000 + 1) + 100000)); //傳送簡訊,放入子執行緒中 AsyncManager.getInstance().schedule(AsyncFactory.sendPhoneCode(phone,code),2L); return AxiosResult.success(); }View Code
七、SpringDataRedis的使用
在進行登入驗證的時候,我們不僅需要從前端傳遞過來的驗證碼,還需要我們自己生成的驗證碼進行對照,對於驗證碼我們不可能永久儲存,若是放到MySQL中,不僅是存取麻煩,還有對資料庫的大量操作會造成資料庫的崩潰,這時就需要用到對臨時資料的儲存Redis資料庫。
在spring mvc中對Redis的操作有兩種,使用RedisTemplate操作Redis和使用StringRedisTemplate操作Redis,這兩種無論是哪一種都需要Redis工廠的支援。
在WebConfig類中配置
package com.shangma.cn.config; import com.shangma.cn.interceptor.LoginInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import redis.clients.jedis.JedisPoolConfig; @ComponentScan(basePackages = {"com.shangma.cn.controller", "com.shangma.cn.exception"}) @EnableWebMvc//開啟MVC註解驅動 @Configuration//加入容器 public class WebConfig implements WebMvcConfigurer { /** * Redis工廠 * 如果是本機,可以什麼都不配置 * @return */ @Bean public RedisConnectionFactory redisConnectionFactory() { JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); return jedisConnectionFactory; } /** * 使用RedisTemplate操作Redis * @return */ // @Bean // public RedisTemplate redisTemplate(){ // RedisTemplate redisTemplate = new RedisTemplate(); // redisTemplate.setConnectionFactory(redisConnectionFactory()); // return redisTemplate; // } /** * 使用StringRedisTemplate操作Redis * @return */ @Bean public StringRedisTemplate stringRedisTemplate() { StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); stringRedisTemplate.setConnectionFactory(redisConnectionFactory()); return stringRedisTemplate; } }View Code
登入驗證
//在傳送驗證碼方法中新增 //在生成驗證碼之後 //生成驗證碼,存入Redis,設定2分鐘自動銷燬 stringRedisTemplate.opsForValue().set(phone,code,2, TimeUnit.MINUTES); /** * 登入驗證 * @param map * @param session * @return */ @RequestMapping("doLogin") public AxiosResult<Void> doLogin(@RequestBody Map<String,String>map, HttpSession session){ System.out.println(map); String phone = map.get("phone"); String code = map.get("code"); String s = stringRedisTemplate.opsForValue().get(phone); if (s.equals(code)) { Employee employee = employeeService.findByPhone(phone); //將登陸資訊放入session session.setAttribute("user",employee); //清除 stringRedisTemplate.delete(phone); System.out.println(AxiosResult.success().getData()); return AxiosResult.success(); } return AxiosResult.error(); }View Code
八、Redis的連線池
在WebConfig類中配製
package com.shangma.cn.config; import com.shangma.cn.interceptor.LoginInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import redis.clients.jedis.JedisPoolConfig; /** * @author: JAVASM * @className: WebConfig * @description: * @date: 2021/6/14 13:04 * @version: 0.1 * @since: jdk1.8 */ @ComponentScan(basePackages = {"com.shangma.cn.controller", "com.shangma.cn.exception"}) @EnableWebMvc//開啟MVC註解驅動 @Configuration//加入容器 public class WebConfig implements WebMvcConfigurer { /** * jedis執行緒池 * @return */ @Bean public JedisPoolConfig jedisPoolConfig(){ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(50); jedisPoolConfig.setMaxIdle(50); return jedisPoolConfig; } /** * Redis工廠 * 如果是本機,可以什麼都不配置 * @return */ @Bean public RedisConnectionFactory redisConnectionFactory() { JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig()); return jedisConnectionFactory; } /** * 使用RedisTemplate操作Redis * @return */ // @Bean // public RedisTemplate redisTemplate(){ // RedisTemplate redisTemplate = new RedisTemplate(); // redisTemplate.setConnectionFactory(redisConnectionFactory()); // return redisTemplate; // } /** * 使用StringRedisTemplate操作Redis * @return */ @Bean public StringRedisTemplate stringRedisTemplate() { StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); stringRedisTemplate.setConnectionFactory(redisConnectionFactory()); return stringRedisTemplate; } }View Code
九、使用異常處理的方式來解決不滿足條件的問題
異常可以阻止程式的執行,我們通過自定異常,在捕捉處理這個異常,就能達到解決不滿足條件時程式的執行問題
定義異常
package com.shangma.cn.exception; import com.shangma.cn.common.AxiosStatus; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class MyLoginException extends RuntimeException{ private AxiosStatus axiosStatus; }View Code
捕捉處理異常
package com.shangma.cn.exception; import com.shangma.cn.common.AxiosResult; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class MyHandler { @ExceptionHandler(MyLoginException.class) public AxiosResult<Void> myHandler(MyLoginException e){ return AxiosResult.error(e.getAxiosStatus()); } }View Code
完整版的登入
package com.shangma.cn.controller; import com.shangma.cn.common.AxiosResult; import com.shangma.cn.common.AxiosStatus; import com.shangma.cn.entity.Employee; import com.shangma.cn.exception.MyLoginException; import com.shangma.cn.factory.AsyncFactory; import com.shangma.cn.pool.AsyncManager; import com.shangma.cn.service.EmployeeService; import com.shangma.cn.utils.RandomCodeUtil; import com.shangma.cn.utils.SmsUtil; import com.shangma.cn.utils.UploadUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; import javax.servlet.http.Part; import java.io.IOException; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @author: JAVASM * @className: CommonController * @description: * @date: 2021/6/14 19:15 * @version: 0.1 * @since: jdk1.8 */ @RestController @RequestMapping("common") public class CommonController { @Autowired private EmployeeService employeeService; @Autowired private StringRedisTemplate stringRedisTemplate; /** * 傳送驗證碼 * @param phone * @return */ @RequestMapping("getCode/{phone}") public AxiosResult<Void> getCode(@PathVariable String phone){ System.out.println(phone); Employee employee = employeeService.findByPhone(phone); System.out.println(employee); if (employee==null) { throw new MyLoginException(AxiosStatus.PHONE_NOT_FOUND); } //生成驗證碼,存入Redis,設定2分鐘自動銷燬 String code = RandomCodeUtil.munCode(); stringRedisTemplate.opsForValue().set(phone,code,2, TimeUnit.MINUTES); //傳送簡訊,放入子執行緒中 AsyncManager.getInstance().schedule(AsyncFactory.sendPhoneCode(phone,code),2L); return AxiosResult.success(); } /** * 登入驗證 * @param map * @param session * @return */ @RequestMapping("doLogin") public AxiosResult<Void> doLogin(@RequestBody Map<String,String>map, HttpSession session){ System.out.println(map); String phone = map.get("phone"); String code = map.get("code"); String s = stringRedisTemplate.opsForValue().get(phone); if (s.equals(code)) { Employee employee = employeeService.findByPhone(phone); //將登陸資訊放入session session.setAttribute("user",employee); //清除 stringRedisTemplate.delete(phone); System.out.println(AxiosResult.success().getData()); return AxiosResult.success(); } throw new MyLoginException(AxiosStatus.CODE_CHECK_ERROR); } }View Code
十、手動列印日誌
在專案釋出後沒有控制檯讓我們檢視BUG,此時我們就需要記錄程式的報錯資訊
一般我們使用log4j來記錄錯誤資訊
先匯入lombok的jar,在需要記錄日誌的類上加上@Log4j就可以直接使用了
例如
try { client.sendSms(sendSmsRequest); } catch (Exception e) { e.printStackTrace(); log.error(phone + "+手機驗證碼傳送失敗:" + e.getMessage()); }
十一、跨域問題的解決以及登入攔截的實現
十二、分頁查詢以及日期時區問題的解決
前端部分頁面
<div class="box-body"> <table id="example2" class="table table-bordered table-hover"> <thead> <tr> <th><input type="checkbox" @change="chooseAll"/>全選</th> <th>id</th> <th>頭像</th> <th>姓名</th> <th>手機號</th> <th>工資</th> <th>地址</th> <th>入職時間</th> <th>操作</th> </tr> </thead> <tbody ref="table_Body"> <tr v-for="(item,index) in tableData" :key="index"> <td><input type="checkbox" class="check_td" @click="chooseDeleteItem(item.employeeId,$event)"></td> <td class="id_td">{{item.employeeId}}</td> <td><img :src="item.employeeAvatar" width="35px" height="35px"></td> <td>{{item.employeeName}}</td> <td>{{item.employeePhone}}</td> <td>{{item.employeeSalary}}</td> <td>{{item.employeeAddress}}</td> <td>{{item.employeeTime}}</td> <td> <button class="btn btn-sm btn-info" @click="findById(item.employeeId)" data-toggle="modal" data-target="#editDialog"> 編輯 </button> <button class="btn btn-sm btn-warning" @click="doDeleteBtn(item.employeeId)" data-toggle="modal" data-target="#delModal"> 刪除 </button> </td> </tr> </tbody> </table> </div>View Code
前端部分JS程式碼
let vue = new Vue({ el: "#app", data: { tableData: [], currentPage: 1, pageSize: 5, total: 0, imgUrl: '', formData: {}, deleteIds: [] }, created() { this.findPage(); }, methods: { //查詢全部 findAll() { myaxios.get(`employee/findAll`).then(response => { let {data} = response; this.tableData = data; }) }, //分頁查詢 findPage() { myaxios.get(`employee/findPage?currentPage=${this.currentPage}&pageSize=${this.pageSize}`).then(response => { let {total, list} = response; this.total = total; this.tableData = list; }) }, //分頁回掉函式 pageChange(page) { this.currentPage = page; var elementsByClassName = this.$refs.table_Body.getElementsByClassName("check_td"); for (let i = 0; i < elementsByClassName.length; i++) { elementsByClassName[i].checked=false; } this.findPage(); } }View Code
後臺
Service層
@Override public List<Employee> findAll() { List<Employee> employees = employeeMapper.selectByExample(null); return employees; } @Override public PageBean<Employee> findPage() { List<Employee> employees = employeeMapper.selectByExample(null); PageInfo<Employee> pageInfo = new PageInfo<>(employees); return PageBean.init(pageInfo.getTotal(),employees); }View Code
Controller層
/** * 查詢全部 * * @return */ @GetMapping("findAll") public AxiosResult<List<Employee>> findAll() { List<Employee> all = employeeService.findAll(); return AxiosResult.success(all); } /** * 分頁查詢 * * @param currentPage 當前頁 * @param pageSize 每一頁大小 * @return */ @GetMapping("findPage") public AxiosResult<PageBean<Employee>> findPage(int currentPage, int pageSize) { //開啟分頁 PageHelper.startPage(currentPage, pageSize); PageBean<Employee> all = employeeService.findPage(); return AxiosResult.success(all); }View Code
這裡我們查詢後,會發現時間會和資料庫中的不同,這是因為資料庫的DateTime型別沒有時區(time zone)資訊,因此,存入的是本地時間,並且丟掉了時區資訊。如果你把資料庫伺服器的時區改了,或者把應用伺服器的時區改了,讀出來的日期和時間就是錯誤的。所以這裡我們要上設定時區,
在實體類相應的時間屬性上加上註解
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "CMT+8")
因為是maven工程,在依賴了相關Jackson的jar後,會自動幫我們依賴
十三、檔案上傳——使用阿里雲物件儲存
前端部分JS程式碼
//選擇圖片上傳 chooseAvatar(e) { let file = e.target.files[0]; let formData = new FormData(); formData.append("avatar", file); myaxios.post("common/upload/", formData, {headers: {"Content-Type": "multipart/form-data"}}) .then(response => { // console.log(response) this.imgUrl = response; this.formData.employeeAvatar = response; }) }View Code
工具類(需要依賴阿里雲相關jar)
package com.shangma.cn.utils; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * @author: JAVASM * @className: UploadUtil * @description: * @date: 2021/6/15 21:34 * @version: 0.1 * @since: jdk1.8 */ public class UploadUtil { private static Properties properties; static { //載入配置檔案 try { properties = new Properties(); properties.load(SmsUtil.class.getClassLoader().getResourceAsStream("aliyun-oss.properties")); } catch (IOException e) { e.printStackTrace(); } } /** * 檔案上傳 * @param fileName 檔名 * @param in 流 * @return */ public static String upload(String fileName, InputStream in) { String endpoint = properties.getProperty("aliyun-oss.endpoint"); String accessKeyId = properties.getProperty("aliyun-oss.accessKeyId"); String accessKeySecret = properties.getProperty("aliyun-oss.accessKeySecret"); OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); //第一引數: 表示bucket名稱 //第二個引數: 檔名稱 攜帶字尾 ossClient.putObject(properties.getProperty("aliyun-oss.bucketName"), fileName, in); ossClient.shutdown(); String url = properties.getProperty("aliyun-oss.imgUrl") + fileName; return url; } }View Code
Controller層
/** * 上傳檔案 * @param avatar * @return * @throws IOException */ @RequestMapping("upload") public AxiosResult<String> upload(@RequestPart Part avatar) throws IOException { //重新命名 // TODO: 2021/6/15 不能上傳檔案 System.out.println(avatar); String fileName = System.nanoTime() + "." + StringUtils.getFilenameExtension(avatar.getSubmittedFileName()); String url = UploadUtil.upload(fileName, avatar.getInputStream()); return AxiosResult.success(url); }View Code
十四、新增員工資訊
前端頁面
<div class="box-header"> <div class="form-group form-inline"> <div class="btn-group"> <button type="button" class="btn btn-danger" title="新建" @click="formData={},imgUrl=''" data-toggle="modal" data-target="#editDialog">新建 </button> <button type="button" class="btn btn-success" data-toggle="modal" data-target="#delModal">批量刪除 </button> <button type="button" class="btn btn-primary">匯入</button> <button type="button" class="btn btn-info">匯出</button> </div> </div> </div> 模態框 <!--新建 編輯彈框--> <div class="modal fade" id="editDialog"> <div class="modal-dialog modal-lg edit-dialog"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title">員工操作</h4> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div class="card-body"> <div class="form-group row"> <label class="col-sm-2 col-form-label">員工姓名:</label> <div class="col-sm-10"> <input type="text" v-model="formData.employeeName" class="form-control" placeholder="請輸入員工姓名"> </div> </div> <div class=" form-group row"> <label class="col-sm-2 col-form-label">員工手機:</label> <div class="col-sm-10"> <input type="text" v-model="formData.employeePhone" class="form-control" placeholder="請輸入員工手機"/> </div> </div> <div class=" form-group row"> <label class="col-sm-2 col-form-label">員工工資:</label> <div class="col-sm-10"> <input type="text" v-model="formData.employeeSalary" class="form-control" placeholder="請輸入員工工資"/> </div> </div> <div class=" form-group row"> <label class="col-sm-2 col-form-label">員工地址:</label> <div class="col-sm-10"> <input type="text" v-model="formData.employeeAddress" class="form-control" placeholder="請輸入員工地址"/> </div> </div> <div class=" form-group row"> <label class="col-sm-2 col-form-label">入職時間:</label> <div class="col-sm-10"> <input id="date" v-model="formData.employeeTime" type="date" class="form-control"/> </div> </div> <div class=" form-group row"> <label class="col-sm-2 col-form-label">員工頭像</label> <div class="col-sm-10"> <label class="btn btn-primary"> <input type="file" @change="chooseAvatar" style="display:none;" id="avater"> 上傳圖片 </label> <img :src="imgUrl" alt="" width="100px" height="100px" style="border: 1px solid #ccc; margin-left: 100px"> </div> </div> </div> </div> <div class="modal-footer "> <button type="button" class="btn btn-default" data-dismiss="modal">取消</button> <button type="button" class="btn btn-success" @click="addOrEdit" data-dismiss="modal">確定</button> </div> </div> </div> </div>View Code
前端JS
//新增或修改確認按鈕 addOrEdit() { if (this.formData.employeeId) { //修改 } else { //新增 //如果沒有圖片,新增預設圖片 this.formData.employeePassword='123456'; if (this.formData.employeeAvatar) { myaxios.post(`employee/addEmployee/`, this.formData) .then(response => { this.findPage(); }); } else { this.formData.employeeAvatar = 'https://shangmasanshiqi.oss-cn-beijing.aliyuncs.com/282285958269457.gif'; myaxios.post(`employee/addEmployee/`, this.formData) .then(response => { this.findPage(); }); } } }View Code
Service
@Override public void addEmployee(Employee employee) { employeeMapper.insert(employee); }
Controller
/** * 新增 * @param employee * @return */ @PostMapping("addEmployee") public AxiosResult<Void> addEmployee(@RequestBody Employee employee) { employeeService.addEmployee(employee); return AxiosResult.success(); }
十五、改造前端Axios以及解決返回值的問題
自定義Axios,可以解決重複寫基本路徑、設定是否攜帶cookie,設定請求時間,設定攔截器(請求之前攔截,返回值響應攔截),
let myaxios = axios.create({ //設定基礎路徑 baseURL:'http://localhost:8080/', //設定攜帶cookie withCredentials:true, //設定請求時間 timeout:5000 }); // 請求之前攔截 myaxios.interceptors.request.use(function (config) { // config.baseURL='http://localhost:8080/'; return config; }, function (error) { return Promise.reject(error); }); // 響應攔截器 myaxios.interceptors.response.use(function (response) { console.log(response); let{status,massage,data}=response.data; if (status==2000){ //console.log(data) return data; } if (status==4004){ alert("登入過期,請重新登入") location.replace("./login.html"); } else { console.log(massage); //阻止程式碼向下執行 return Promise.reject(false); } }, function (error) { return Promise.reject(error); });View Code
當我們給前端返回資料的時候,data有可能是null,這就有可能在前端產生錯誤,因此,我們需要在返回資料時進行一次判斷,返回不為空的值
我們在返回值進行json序列化的時候,只序列化有資料的,在返回值類AxiosResult上加@JsonInclude(JsonInclude.Include.NON_NULL)就可以進行過濾。
十六、修改員工資訊
前端頁面同查詢和新增
前端JS
//新增或修改確認按鈕 addOrEdit() { if (this.formData.employeeId) { //修改 myaxios.put(`employee/updateEmployee/`, this.formData).then(() => { this.findPage(); }) } else { //新增 //如果沒有圖片,新增預設圖片 this.formData.employeePassword='123456'; if (this.formData.employeeAvatar) { myaxios.post(`employee/addEmployee/`, this.formData) .then(response => { this.findPage(); }); } else { this.formData.employeeAvatar = 'https://shangmasanshiqi.oss-cn-beijing.aliyuncs.com/282285958269457.gif'; myaxios.post(`employee/addEmployee/`, this.formData) .then(response => { this.findPage(); }); } } }, //修改按鈕點選事件 findById(id) { myaxios.get(`employee/findById/${id}`).then(response => { this.formData = response; //沒有圖片新增預設圖片, if (response.employeeAvatar) { this.imgUrl = response.employeeAvatar; } else { this.imgUrl = 'https://shangmasanshiqi.oss-cn-beijing.aliyuncs.com/282285958269457.gif'; this.formData.employeeAvatar = 'https://shangmasanshiqi.oss-cn-beijing.aliyuncs.com/282285958269457.gif'; } }) }View Code
Service
@Override public Employee findById(Integer id) { Employee employee = employeeMapper.selectByPrimaryKey(id); return employee; } @Override public void update(Employee employee) { employeeMapper.updateByPrimaryKey(employee); }View Code
Controller
/** * 根據id查詢資訊 * @param id * @return */ @GetMapping("findById/{id}") public AxiosResult<Employee> findById(@PathVariable Integer id) { Employee employee = employeeService.findById(id); return AxiosResult.success(employee); } /** * 修改 * @param employee * @return */ @RequestMapping("updateEmployee") public AxiosResult<Void> updateEmployee(@RequestBody Employee employee){ employeeService.update(employee); return AxiosResult.success(); }View Code
十七、刪除和批量刪除員工資訊
前端頁面同修改,多了一個模態框
<!--刪除彈框--> <div class="modal fade" id="delModal"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title">溫馨提示</h4> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> 你確定要刪除嗎? </div> <div class="modal-footer "> <button type="button" class="btn btn-default" data-dismiss="modal">取消</button> <button type="button" @click="delBtn" class="btn btn-success delSure" data-dismiss="modal">確定 </button> </div> </div> </div> </div>View Code
前端JS
//獲取刪除元素 chooseDeleteItem(id, e) { if (e.target.checked) { this.deleteIds.push(id); } else { this.deleteIds.splice(this.deleteIds.findIndex(item => item == id, 1)) } }, //點選刪除按鈕 doDeleteBtn(id) { //獲取checkbox的值 var elementsByClassName = this.$refs.table_Body.getElementsByClassName("check_td"); for (let i = 0; i < elementsByClassName.length; i++) { elementsByClassName[i].checked=false; } this.deleteIds=[]; this.deleteIds.push(id); }, //刪除確認按鈕 delBtn(){ myaxios.delete(`employee/deleteByIds/${this.deleteIds}`).then(()=>{ console.log(this.tableData.length) if (this.tableData.length==1){ if (this.currentPage>1){ this.currentPage=this.currentPage-1 } } this.findPage() }) }, //全選 chooseAll(e){ if (e.target.checked) { var elementsByClassName = this.$refs.table_Body.getElementsByClassName("check_td"); for (let i = 0; i < elementsByClassName.length; i++) { elementsByClassName[i].checked=true; } //清空陣列 this.deleteIds=[]; //新增到陣列 var elementsByClassName1 = this.$refs.table_Body.getElementsByClassName("id_td"); for (let i = 0; i < elementsByClassName1.length; i++) { this.deleteIds.push(elementsByClassName1[i].outerText); } console.log(this.deleteIds) } else { var elementsByClassName = this.$refs.table_Body.getElementsByClassName("check_td"); for (let i = 0; i < elementsByClassName.length; i++) { elementsByClassName[i].checked=false; } //清空陣列 this.deleteIds=[]; } }View Code
Service
@Override public void deleteByIds(List<Integer> ids) { EmployeeExample employeeExample = new EmployeeExample(); EmployeeExample.Criteria criteria = employeeExample.createCriteria(); criteria.andEmployeeIdIn(ids); employeeMapper.deleteByExample(employeeExample); }
Controller
@DeleteMapping("deleteByIds/{ids}") public AxiosResult<Void> deleteByIds(@PathVariable List<Integer> ids){ employeeService.deleteByIds(ids); return AxiosResult.success(); }