[Day 06] - 用Spring Boot 建立Controller

回顾前一天讲的MVC,下达request到Controller後,由Service去执行资料的 增/删/改/查,最後再呈现在View上

在以前,我们会新增一支class继承 HttpServlet,然後实作doGet、doPost,
除了要写一堆if判断request并做出对应的操作、还要负责页面的转导等等工作

现在有了spring boot,而且做的是前後端分离,
只要在Controller做出能让前端好call的api就行了,做起来也相对简单明了

今天就来做User的登入api
这边我想仿作银行的三层式帐密(ID+User+Password),

在这之前,我有在Service再补加一个verifyUser的方法,是用来验证登入帐号密码的,
其实就只是简单的检查字串,

实际上银行的帐号密码验证是不会这麽阳春的,
而且传入Server的值会在client端就先加密,
确保封包不会被侧录(end-to-end encryption),
总之很安全,有空的话就再来试着实作简单版的ETOEE

    public String verifyUser(String email,String userAcct , String userPasswd){
        /*
         0000 login success
         0001 wrong email
         0002 wrong useracct
         0003 wrong passwd
         0004 acct locked
         9999 unknown error
         */
        Optional<User> user=userRepo.findUserByEmail(email);
        String result="9999";
        if(user.isPresent()){
            if(user.get().getUserAccount().equals(userAcct)){
                if(user.get().getUserPassword().equals(userPasswd)){
                    result="0000";
                }else{
                    result="0003";
                }
            }else{
                result="0002";
            }
        }else{
            result="0001";
        }
        return result;        
    }

本来想再加个密码错5次就锁定帐号跟检查帐号状态,之後有时间再补充好了

接着来到controller,今天要实作登入的api

@RestController
@RequestMapping("api/user")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;
    @PostMapping("/login")
    public ResponseEntity<String> userLogin(@RequestBody Map<String,String> map){
        String email= map.get("email");
        String userAccount= map.get("userAccount");
        String userPassword= map.get("userPassword");
        String result="null data";
        if(StringUtils.isBlank(email)||StringUtils.isBlank(userAccount)||StringUtils.isBlank(userPassword)){
            return ResponseEntity.ok(result);
        }else{
            result=userService.verifyUser(email, userAccount, userPassword);
            return ResponseEntity.ok(result);  
        }
    }   
    
}

在这边我用了5个annotation:

@RestController用来告诉Spring这是一个RestController,

@RestController@Controller的区别是
@RestController下的方法return的值会直接被预设为Response body回传
@RestController也等於是@Controller+@ResponseBody

@RestController适用於api这样直接回传值的需求
@Controller则是适合用在jsp、thymeleaf这种Server同时也负责页面渲染的情况

接着,当Spring知道这是一个Controller後就会去检查有没有@RequestMapping
并将这个Controller绑在相对的url上,像我设定的是"api/user",
因此会对应到的url是 localhost:8080/api/user

@RequiredArgsConstructor跟昨天在Service的用法一样是用来实体化物件用的就不赘述

@PostMapping应该很好理解
就是对应浏览器的HTTP method的实作

相关的有:
@GetMapping
@PostMapping
@DeleteMapping
@PutMapping
@PatchMapping

我在这边决定以POST方法来实做登入功能,
@PostMapping("/login")对应到 localhost:8080/api/user/login

    @PostMapping("/login")
    public ResponseEntity<String> userLogin(@RequestBody HashMap<String,String> map){
        String email= map.get("email");
        String userAccount= map.get("userAccount");
        String userPassword= map.get("userPassword");
        String result="请输入帐号密码!";
        if(StringUtils.isBlank(email)||StringUtils.isBlank(userAccount)||StringUtils.isBlank(userPassword)){
            return ResponseEntity.ok(result);
        }else{
            result=userService.verifyUser(email, userAccount, userPassword);
            return ResponseEntity.ok(result);  
        }
    } 

@RequestBody会接收Post发送过来的request Body,我选择以HashMap来储存,
(其实应该要用User这个Object来存较好,
用HashMap的话除了无法对传入的值做最低限度的限制,也降低程序码的可读性)

Spring会将requestBody转为Map的形式传入

虽然到时候在前端会检查,不过我在这边还是有做一下null检核,

org.apache.commons.lang3.StringUtils,好用!
要记得在pom.xml加个dependency

		<dependency>
    		<groupId>org.apache.commons</groupId>
   			<artifactId>commons-lang3</artifactId>
   		 	<version>3.9</version>
		</dependency>

本来在想登入成功跟失败的Response Code是不是要有区别比较好,不过还没想到前端到时候会怎麽做
就决定先全部都回200,也就是ok

在测试api时发现post方法一直回传"status":403,"error":"Forbidden"
https://ithelp.ithome.com.tw/upload/images/20210921/20128973489ebywS22.png

查了一下发现原来是因为Spring Security的预设设定有CSRF的保护
会阻挡没带CSRF token的POST、PATCH、PUT、DELETE等http method

为求测试方便,先把这个功能关闭,顺便也关掉Spring Security的登入页

建立一个WebSecurityConfig.java,

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .anyRequest().permitAll() //让所有页面都不用登入
            .and().csrf().disable(); //关掉csrf保护
    }  
}

接着再测就成功把post送到server了
https://ithelp.ithome.com.tw/upload/images/20210921/201289734d9OStflWW.png

回0000,表示帐号密码正确

今天就先这样吧!


<<:  Day07 NAT 类型

>>:  每个人都该学的30个Python技巧|技巧 21:set的处理方法(字幕、衬乐、练习)

[Day 27] 组件基础(2)

今天也要来讲解组件的基础,基础打好未来学习新的东西才不会搞得东倒西歪,虽然今天已经27天了o(^▽^...

[Day28] 打造高效团队,先累积社会资本

「欢迎来到 XX 的大家庭,希望大家把团队当作家人,一起成长……」 这是在某间公司报到时,HR 对我...

进击的软件工程师之路-软件战斗营 第十一周

学习进度 Android Studio View(View、Image view、Text view...

day20 在ui蒐集flow,能取代liveData吗?

好的,前一篇讲到了flow可以完全取代liveData,其实错!! 直接从结论开始讲,flow并不支...

Day06 建构Project(2)

我们昨天建立完Project後,我们再仔细深入研究的话,会发现第二层的Test里面还有其他额外的四个...