赞
踩
秒杀,对我们来说,都不是一个陌生的东西。每年的双11,618以及时下流行的直播等等。秒杀然而,这 对于我们系统而言是一个巨大的考验。
那么,如何才能更好地理解秒杀系统呢?我觉得作为一个程序员,你首先需要从高维度出发,从整体上 思考问题。在我看来,秒杀其实主要解决两个问题,一个是并发读,一个是并发写。并发读的核心优化 理念是尽量减少用户到服务端来“读”数据,或者让他们读更少的数据;并发写的处理原则也一样,它要 求我们在数据库层面独立出来一个库,做特殊的处理。另外,我们还要针对秒杀系统做一些保护,针对 意料之外的情况设计兜底方案,以防止最坏的情况发生。
其实,秒杀的整体架构可以概括为“稳、准、快”几个关键字。
首先,就是整个系统架构要满足高可用,流量符合预期时肯定要稳定,就是超出预期时也同样不能掉链子,你要保证秒杀活动顺利完成,即秒杀商品顺利地卖出去,这个是最基本的前提。
然后,就是“准”,就是秒杀 10 台 iPhone,那就只能成交 10 台,多一台少一台都不行。一旦库存不对, 那平台就要承担损失,所以“准”就是要求保证数据的一致性。
最后,再看“快”,“快”其实很好理解,它就是说系统的性能要足够高,否则你怎么支撑这么大的流量呢? 不光是服务端要做极致的性能优化,而且在整个请求链路上都要做协同的优化,每个地方快一点,整个 系统就完美了。
所以从技术角度上看“稳、准、快”,就对应了我们架构上的高可用、一致性和高性能的要求
高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。对应的方案比如动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化
一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知
高可用。 现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,我们 还要设计一个 PlanB 来兜底,以便在最坏情况发生时仍然能够从容应对。
Spring项目,JDK1.8,勾选Spring Web,Thymeleaf,Lombok
- <!--mybatisplus依赖-->
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.4.0</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- <version>5.1.49</version>
- </dependency>
spring: # thymelaef配置 thymeleaf: # 关闭缓存 cache: false # 数据源配置 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seckill?useUnicode=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 hikari: #连接池名 pool-name: DateHikariCP # 最小空闲连接出 minimum-idle: 5 # 空闲连接存活最大时间,默认600000(10分钟) idle-timeout: 600000 #最大连接数,默认10 maximum-pool-size: 10 # 从连接池返回的连接自动提交 auto-commit: true # 连接最大存活时间,0表示永久存活,默认1800000(30分钟) max-lifetime: 1800000 # 连接超时时间,默认30000(30秒) connection-timeout: 30000 # 测试连接是否可用的查询语句 connection-test-query: SELECT 1 #Mybatis-plus配置 mybatis-plus: # 配置Mapper.xml映射文件 mapper-locations: classpath*:/mapper/*Mapper.xml # 配置MyBatis数据返回类型别名(默认别名是类名) type-aliases-package: com.xxxx.seckill.pojo # MyBatis SQL打印(方法接口所在的包,不是Mapper.xml所在的包) logging: level: com.xxxx.seckill.mapper: debug
创建DemoController.java
- @Controller
- @RequestMapping("/demo")
- public class DemoController {
- /**
- * 功能描述: 测试页面跳转
- */
- @RequestMapping("/hello")
- public String hello(Model model){
- model.addAttribute("name","xxxx");
- return "hello";
- }
- }
创建hello.html
- <!DOCTYPE html>
- <html lang="en"
- xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>测试</title>
- </head>
- <body>
- <p th:text="'hello:'+${name}"></p>
- </body>
- </html>
客户端:PASS=MD5(明文+固定Salt)
服务端:PASS=MD5(用户输入+随机Salt)
用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。
导入依赖
- <!-- md5 依赖 -->
- <dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.6</version>
- </dependency>
编写MD5工具类
创建utils文件夹,创建MD5Util.java
- @Component
- public class MD5Util {
-
- public static String md5(String src){
- return DigestUtils.md5Hex(src);
- }
-
- private static final String salt="1a2b3c4d";
-
- public static String inputPassToFromPass(String inputPass){
- String str = "" +salt.charAt(0)+salt.charAt(2)+inputPass+salt.charAt(5)+salt.charAt(4);
- return md5(str);
- }
-
- public static String formPassToDBPass(String formPass,String salt){
- String str = "" +salt.charAt(0)+salt.charAt(2)+formPass+salt.charAt(5)+salt.charAt(4);
- return md5(str);
- }
-
- public static String inputPassToDBPass(String inputPass,String salt){
- String fromPass = inputPassToFromPass(inputPass);
- String dbPass = formPassToDBPass(fromPass, salt);
- return dbPass;
- }
-
-
- public static void main(String[] args) {
- // d3b1294a61a07da9b49b6e22b2cbd7f9
- System.out.println(inputPassToFromPass("123456"));
- System.out.println(formPassToDBPass("d3b1294a61a07da9b49b6e22b2cbd7f9","1a2b3c4d"));
- System.out.println(inputPassToDBPass("123456","1a2b3c4d"));
- }
- }
创建数据库
数据库:seckill,创建t_user表
- CREATE TABLE t_user(
- `id` BIGINT(20) NOT NULL COMMENT '用户ID shoujihaoma',
- `nickname` VARCHAR(255) not NULL,
- `pasword` VARCHAR(32) DEFAULT NULL COMMENT 'MD5二次加密',
- `slat` VARCHAR(10) DEFAULT NULL,
- `head` VARCHAR(128) DEFAULT NULL COMMENT '头像',
- `register_date` datetime DEFAULT NULL COMMENT '注册时间',
- `last_login_date` datetime DEFAULT NULL COMMENT '最后一次登录时间',
- `login_count` int(11) DEFAULT '0' COMMENT '登录次数',
- PRIMARY KEY(`id`)
- );
逆向工程
首先需要通过逆向工程基于 t_user 表生产对应的POJO、Mapper、Service、ServiceImpl、Controller 等类,项目中使用了MybatisPlus,所以逆向工程也是用了MybatisPlus提供的AutoGenerator,代码如 下。具体可去官网查看
创建新项目generator
添加依赖
- <!--mybatisplus依赖-->
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.4.0</version>
- </dependency>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-generator</artifactId>
- <version>3.4.0</version>
- </dependency>
- <dependency>
- <groupId>org.freemarker</groupId>
- <artifactId>freemarker</artifactId>
- <version>2.3.30</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.49</version>
- <scope>runtime</scope>
- </dependency>
创建文件夹generator
创建CodeGenerator.java
- /**
- * 执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
- */
- public class CodeGenerator {
- /**
- * <p>
- * 读取控制台内容
- * </p>
- */
- public static String scanner(String tip) {
- Scanner scanner = new Scanner(System.in);
- StringBuilder help = new StringBuilder();
- help.append("请输入" + tip + ":");
- System.out.println(help.toString());
- if (scanner.hasNext()) {
- String ipt = scanner.next();
- if (StringUtils.isNotBlank(ipt)) {
- return ipt;
- }
- }
- throw new MybatisPlusException("请输入正确的" + tip + "!");
- }
-
- public static void main(String[] args) {
- // 代码生成器
- AutoGenerator mpg = new AutoGenerator();
- // 全局配置
- GlobalConfig gc = new GlobalConfig();
- String projectPath = System.getProperty("user.dir");
- gc.setOutputDir(projectPath + "/src/main/java");
- //作者
- gc.setAuthor("sxs");
- //打开输出目录
- gc.setOpen(false);
- //xml开启 BaseResultMap
- gc.setBaseResultMap(true);
- //xml 开启BaseColumnList
- gc.setBaseColumnList(true);
- //日期格式,采用Date
- gc.setDateType(DateType.ONLY_DATE);
- mpg.setGlobalConfig(gc);
- // 数据源配置
- DataSourceConfig dsc = new DataSourceConfig();
- dsc.setUrl("jdbc:mysql://localhost:3306/seckill?useUnicode=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
- dsc.setDriverName("com.mysql.jdbc.Driver");
- dsc.setUsername("root");
- dsc.setPassword("123456");
- mpg.setDataSource(dsc);
- // 包配置
- PackageConfig pc = new PackageConfig();
- pc.setParent("com.xxxx.seckill")
- .setEntity("pojo")
- .setMapper("mapper")
- .setService("service")
- .setServiceImpl("service.impl")
- .setController("controller");
- mpg.setPackageInfo(pc);
- // 自定义配置
- InjectionConfig cfg = new InjectionConfig() {
- @Override
- public void initMap() {
- // to do nothing
- Map<String, Object> map = new HashMap<>();
- map.put("date1", "1.0.0");
- this.setMap(map);
- }
- };
- // 如果模板引擎是 freemarker
- String templatePath = "/templates/mapper.xml.ftl";
- // 如果模板引擎是 velocity
- // String templatePath = "/templates/mapper.xml.vm";
- // 自定义输出配置
- List<FileOutConfig> focList = new ArrayList<>();
- // 自定义配置会被优先输出
- focList.add(new FileOutConfig(templatePath) {
- @Override
- public String outputFile(TableInfo tableInfo) {
- // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
- return projectPath + "/src/main/resources/mapper/" +
- tableInfo.getEntityName() + "Mapper"
- + StringPool.DOT_XML;
- }
- });
- cfg.setFileOutConfigList(focList);
- mpg.setCfg(cfg);
- // 配置模板
- TemplateConfig templateConfig = new TemplateConfig()
- .setEntity("templates/entity2.java")
- .setMapper("templates/mapper2.java")
- .setService("templates/service2.java")
- .setServiceImpl("templates/serviceImpl2.java")
- .setController("templates/controller2.java");
- templateConfig.setXml(null);
- mpg.setTemplate(templateConfig);
- // 策略配置
- StrategyConfig strategy = new StrategyConfig();
- //数据库表映射到实体的命名策略
- strategy.setNaming(NamingStrategy.underline_to_camel);
- //数据库表字段映射到实体的命名策略
- strategy.setColumnNaming(NamingStrategy.underline_to_camel);
- //lombok模型
- strategy.setEntityLombokModel(true);
- //生成 @RestController 控制器
- // strategy.setRestControllerStyle(true);
- strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
- strategy.setControllerMappingHyphenStyle(true);
- //表前缀
- strategy.setTablePrefix("t_");
- mpg.setStrategy(strategy);
- mpg.setTemplateEngine(new FreemarkerTemplateEngine());
- mpg.execute();
- }
- }
导入模板
复制到
运行后,将得到的文件夹复制回我们的秒杀项目中
登录功能编写
LoginController.java
- @Controller
- @RequestMapping("/login")
- @Slf4j
- public class LoginController {
-
- @Autowired
- private IUserService userService;
- /*
- * 跳转登录页面
- * */
- @RequestMapping("/toLogin")
- public String toLogin(){
- return "login";
- }
- /**
- * 登录
- * @return
- */
- @RequestMapping("/doLogin")
- @ResponseBody
- public RespBean doLogin(LoginVo loginVo) {
- log.info(loginVo.toString());
- return userService.login(loginVo);
- }
- }
导入编写好的静态资源
login.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>登录</title> <!-- jquery --> <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script> <!-- bootstrap --> <link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}"/> <script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"> </script> <!-- jquery-validator --> <script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script> <script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script> <!-- layer --> <script type="text/javascript" th:src="@{/layer/layer.js}"></script> <!-- md5.js --> <script type="text/javascript" th:src="@{/js/md5.min.js}"></script> <!-- common.js --> <script type="text/javascript" th:src="@{/js/common.js}"></script> </head> <body> <form name="loginForm" id="loginForm" method="post" style="width:50%; margin:0 auto"> <h2 style="text-align:center; margin-bottom: 20px">用户登录</h2> <div class="form-group"> <div class="row"> <label class="form-label col-md-4">请输入手机号码</label> <div class="col-md-5"> <input id="mobile" name="mobile" class="form-control" type="text" placeholder="手机号码" required="true" minlength="11" maxlength="11"/> </div> <div class="col-md-1"> </div> </div> </div> <div class="form-group"> <div class="row"> <label class="form-label col-md-4">请输入密码</label> <div class="col-md-5"> <input id="password" name="password" class="form-control" type="password" placeholder="密码" required="true" minlength="6" maxlength="16"/> </div> </div> </div> <div class="row"> <div class="col-md-5"> <button class="btn btn-primary btn-block" type="reset" onclick="reset()">重置</button> </div> <div class="col-md-5"> <button class="btn btn-primary btn-block" type="submit" onclick="login()">登录</button> </div> </div> </form> </body> <script> function login() { $("#loginForm").validate({ submitHandler: function (form) { doLogin(); } }); } function doLogin() { g_showLoading(); var inputPass = $("#password").val(); var salt = g_passsword_salt; var str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4); var password = md5(str); $.ajax({ url: "/login/doLogin", type: "POST", data: { mobile: $("#mobile").val(), password: password }, success: function (data) { layer.closeAll(); if (data.code == 200) { layer.msg("成功"); window.location.href="/goods/toList" } else { layer.msg(data.message); } }, error: function () { layer.closeAll(); } }); } </script> </html>
此时可以正常进入登录界面
添加公共返回对象
创建vo文件夹
创建RespBeanEnum.java
- @Getter
- @ToString
- @AllArgsConstructor
- public enum RespBeanEnum {
- //通用
- SUCCESS(200, "SUCCESS"),
- ERROR(500, "服务端异常"),
- //登录模块5002xx
- LOGIN_ERROR(500210, "用户名或密码不正确"),
- MOBILE_ERROR(500211, "手机号码格式不正确"),
- BIND_ERROR(500212, "参数校验异常"),
- MOBILE_NOT_EXIST(500213, "手机号码不存在"),
- PASSWORD_UPDATE_FAIL(500214, "密码更新失败"),
- SESSION_ERROR(500215, "用户不存在");
-
- private final Integer code;
- private final String message;
- }
创建RespBean.java
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class RespBean {
-
- private long code;
- private String message;
- private Object obj;
-
- /**
- * 功能描述: 成功返回结果
- */
- public static RespBean success(){
- return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(),null);
- }
-
- /**
- * 功能描述: 成功返回结果
- */
- public static RespBean success(Object obj){
- return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBean.success().getMessage(),obj);
- }
-
- /**
- * 功能描述: 失败返回结果
- */
- public static RespBean error(RespBeanEnum respBeanEnum){
- return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),null);
- }
-
- /**
- * 功能描述: 失败返回结果
- */
- public static RespBean error(RespBeanEnum respBeanEnum,Object obj){
- return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),obj);
- }
-
- }
编写登录参数
LoginVo.java
- /**
- * 登录参数
- */
- @Data
- public class LoginVo {
- private String mobile;
- private String password;
-
- }
此时登录输入参数已经可以得到我们输入的手机号以及加密的密码
添加数据库信息:18012345678 123456
继续编写登录服务类
IUserService.java
- /**
- * 服务类
- */
- public interface IUserService extends IService<User> {
- /**
- * 登录
- * @param loginVo
- * @return
- */
- RespBean login(LoginVo loginVo);
- }
服务实现类
UserServiceImpl.java
- /**
- * 服务实现类
- */
- @Service
- public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements
- IUserService {
- @Autowired
- private UserMapper userMapper;
-
- /**
- * 登录
- * @param loginVo
- * @return
- */
- @Override
- public RespBean login(LoginVo loginVo) {
- String mobile = loginVo.getMobile();
- String password = loginVo.getPassword();
- if (StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)){
- return RespBean.error(RespBeanEnum.LOGINVO_ERROR);
- }
- if (!ValidatorUtil.isMobile(mobile)){
- return RespBean.error(RespBeanEnum.MOBILE_ERROR);
- }
- //根据手机号获取用户
- User user = userMapper.selectById(mobile);
- if (null==user){
- return RespBean.error(RespBeanEnum.LOGINVO_ERROR);
- }
- //校验密码
- if
- (!MD5Util.formPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
- return RespBean.error(RespBeanEnum.LOGINVO_ERROR);
- }
- return RespBean.success();
- }
- }
添加校验手机工具类
- /**
- * 手机号码校验
- */
- public class ValidatorUtil {
-
- private static final Pattern mobile_pattern = Pattern.compile("[1]([3-9])[0-9]{9}$");
-
- public static boolean isMobile(String mobile){
- if (StringUtils.isEmpty(mobile)){
- return false;
- }
- Matcher matcher = mobile_pattern.matcher(mobile);
- return matcher.matches();
- }
-
- }
运行类:
- @SpringBootApplication
- @MapperScan("com.xxxx.seckill.mapper")
- public class SeckillApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(SeckillApplication.class, args);
- }
-
- }
测试登录功能
手机号码格式不正确
密码不正确
正确登录
每个类都写大量的健壮性判断过于麻烦,我们可以使用 validation简化我们的代码
注入依赖
- <!-- validation组件 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-validation</artifactId>
- </dependency>
修改loginVo
- /**
- * 登录参数
- */
- @Data
- public class LoginVo {
- @NotNull
- @IsMobile
- private String mobile;
-
- @NotNull
- @Length(min = 32)
- private String password;
-
- }
自定义注解 IsMobile
创建新文件夹validation,
IsMobile.java
- /**
- * 验证手机号
- */
- @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
- @Retention(RUNTIME)
- @Documented
- @Constraint(validatedBy = {IsMobileValidator.class})
- public @interface IsMobile {
-
- boolean required() default true;
-
- String message() default "手机号码格式错误";
-
- Class<?>[] groups() default { };
-
- Class<? extends Payload>[] payload() default { };
- }
自定义手机号码验证规则
添加在vo中
IsMobileValidator.java
- /**
- * 手机号码校验规则
- */
- public class IsMobileValidator implements ConstraintValidator<IsMobile,String> {
-
- private boolean required = false;
-
- @Override
- public void initialize(IsMobile constraintAnnotation) {
- required = constraintAnnotation.required();
- }
-
- @Override
- public boolean isValid(String value, ConstraintValidatorContext context) {
- if (required){
- return ValidatorUtil.isMobile(value);
- }else {
- if (StringUtils.isEmpty(value)){
- return true;
- }else {
- return ValidatorUtil.isMobile(value);
- }
- }
- }
- }
将userServiceImp的这参数校验注释,
- // // //参数校验
- // if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
- // return RespBean.error(RespBeanEnum.LOGIN_ERROR);
- // }
- // if (!ValidatorUtil.isMobile(mobile)) {
- // return RespBean.error(RespBeanEnum.MOBILE_ERROR);
- // }
重新运行,发现界面没有展示,只在控制输出台显示拦截和异常。
解决:
在LoginController中加上注解 入参添加 @Valid
- @RequestMapping("/doLogin")
- @ResponseBody
- public RespBean doLogin(@Valid LoginVo loginVo) {
- log.info(loginVo.toString());
- return userService.login(loginVo);
- }
测试:
我们知道,系统中异常包括:编译时异常和运行时异常 RuntimeException ,前者通过捕获异常从而获 取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。在开发中,不管是 dao层、service层还是controller层,都有可能抛出异常,在Springmvc中,能将所有类型的异常处理 从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护。 SpringBoot全局异常处理方式主要两种:
使用 @ControllerAdvice 和 @ExceptionHandler 注解。
使用 ErrorController类 来实现
区别:
@ControllerAdvice 方式只能处理控制器抛出的异常。此时请求已经进入控制器中。
ErrorController类 方式可以处理所有的异常,包括未进入控制器的错误,比如404,401等错误
如果应用中两者共同存在,则 @ControllerAdvice 方式处理控制器抛出的异常, ErrorController类 方式处理未进入控制器的异常
@ControllerAdvice 方式可以定义多个拦截方法,拦截不同的异常类,并且可以获取抛出的异常 信息,自由度更大。
使用组合注解:@ControllerAdvice 和 @ExceptionHandler
创建exception文件夹
GlobalException.java
- /**
- * 全局异常
- */
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class GlobalException extends RuntimeException {
- private RespBeanEnum respBeanEnum;
- }
GlobalExceptionHandler.java
- /**
- * 全局异常处理类
- */
- @RestControllerAdvice
- public class GlobalExceptionHandler {
-
- @ExceptionHandler(Exception.class)
- public RespBean ExceptionHandler(Exception e) {
- if (e instanceof GlobalException) {
- GlobalException ex = (GlobalException) e;
- return RespBean.error(ex.getRespBeanEnum());
- } else if (e instanceof BindException) {
- BindException ex = (BindException) e;
- RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
- respBean.setMessage("参数校验异常:" + ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
- return respBean;
- }
- return RespBean.error(RespBeanEnum.ERROR);
- }
-
- }
在RespBeanNum添加异常信息
修改之前代码 直接返回RespBean改为直接抛 GlobalException 异常
- /**
- * 登录
- * @param loginVo
- * @return
- */
- @Override
- public RespBean login(LoginVo loginVo) {
- String mobile = loginVo.getMobile();
- String password = loginVo.getPassword();
- //根据手机号获取用户
- User user = userMapper.selectById(mobile);
- if (null==user){
- throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
- }
- //校验密码
- if
- (!MD5Util.formPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
- throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
- }
- return RespBean.success();
- }
测试
将session信息存放到第三方,客户端和服务端在通信时,去第三方存取session信息。
使用cookie+session记录用户信息
准备工具类
CookieUtil.java
- /**
- * Cookie工具类
- */
- public final class CookieUtil {
-
- /**
- * 得到Cookie的值, 不编码
- *
- * @param request
- * @param cookieName
- * @return
- */
- public static String getCookieValue(HttpServletRequest request, String cookieName) {
- return getCookieValue(request, cookieName, false);
- }
-
- /**
- * 得到Cookie的值,
- *
- * @param request
- * @param cookieName
- * @return
- */
- public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
- Cookie[] cookieList = request.getCookies();
- if (cookieList == null || cookieName == null) {
- return null;
- }
- String retValue = null;
- try {
- for (int i = 0; i < cookieList.length; i++) {
- if (cookieList[i].getName().equals(cookieName)) {
- if (isDecoder) {
- retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
- } else {
- retValue = cookieList[i].getValue();
- }
- break;
- }
- }
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return retValue;
- }
-
- /**
- * 得到Cookie的值,
- *
- * @param request
- * @param cookieName
- * @return
- */
- public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
- Cookie[] cookieList = request.getCookies();
- if (cookieList == null || cookieName == null) {
- return null;
- }
- String retValue = null;
- try {
- for (int i = 0; i < cookieList.length; i++) {
- if (cookieList[i].getName().equals(cookieName)) {
- retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
- break;
- }
- }
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return retValue;
- }
-
- /**
- * 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
- String cookieValue) {
- setCookie(request, response, cookieName, cookieValue, -1);
- }
-
- /**
- * 设置Cookie的值 在指定时间内生效,但不编码
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
- String cookieValue, int cookieMaxage) {
- setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
- }
-
- /**
- * 设置Cookie的值 不设置生效时间,但编码
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
- String cookieValue, boolean isEncode) {
- setCookie(request, response, cookieName, cookieValue, -1, isEncode);
- }
-
- /**
- * 设置Cookie的值 在指定时间内生效, 编码参数
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
- String cookieValue, int cookieMaxage, boolean isEncode) {
- doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
- }
-
- /**
- * 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
- String cookieValue, int cookieMaxage, String encodeString) {
- doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
- }
-
- /**
- * 删除Cookie带cookie域名
- */
- public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
- String cookieName) {
- doSetCookie(request, response, cookieName, "", -1, false);
- }
-
- /**
- * 设置Cookie的值,并使其在指定时间内生效
- *
- * @param cookieMaxage cookie生效的最大秒数
- */
- private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
- String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
- try {
- if (cookieValue == null) {
- cookieValue = "";
- } else if (isEncode) {
- cookieValue = URLEncoder.encode(cookieValue, "utf-8");
- }
- Cookie cookie = new Cookie(cookieName, cookieValue);
- if (cookieMaxage > 0)
- cookie.setMaxAge(cookieMaxage);
- if (null != request) {// 设置域名的cookie
- String domainName = getDomainName(request);
- System.out.println(domainName);
- if (!"localhost".equals(domainName)) {
- cookie.setDomain(domainName);
- }
- }
- cookie.setPath("/");
- response.addCookie(cookie);
- } catch (Exception e) {
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。