论坛的进出系统

整体采用SaToken框架

写登录系统,需要处理以下问题:

  • 后端操作数据库
  • token的设置
  • 后端异常拦截
  • 前端登陆页面
  • 前端request处理

首先,我们先写后端操作数据库部分:

我们采用DDD框架,将后端结构分为apis, application, domain, infrastructure四个分层。

先来写domain层部分:

在domain层(领域层),建立领域实体类Account和相关映射。

1
2
3
4
5
6
7
8
9
10
11
12
//账号实体
@TableName(value = "account", autoResultMap = true)
class Account {
var userId: String = ""
var userName: String = ""
var role: String = ""
var password: String = ""
var deleted: Int = 0
}

@Mapper
interface AccountMapper: BaseMapper<Account>

这里一定记住mysql用_分开定义字段,而kotlin是用驼峰法定义字段。

在建立映射后,写领域服务AccountRepository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//账号的仓库类,提供基本操作服务
@Repository
class AccountRepository(val accountMapper: AccountMapper) {
fun selectAccountByName(userName: String): Account? {
return accountMapper.selectOne(
QueryWrapper<Account>().eq("user_name", userName)
)
}

fun selectAccountById(userId: String): Account? {
return accountMapper.selectOne(
QueryWrapper<Account>().eq("user_id", userId)
)
}

fun insertAccount(account: Account): Int {
return accountMapper.insert(account)
}

fun checkingUserName(userName: String): Boolean {
return accountMapper.exists(
QueryWrapper<Account>().eq("user_name", userName)
)
}
}

这里写了四个方法,用来登录时查询账号,登陆后查询账号,注册账号,查重用户名。

这样domain层部分就写好啦~


开始写后端异常处理器部分

在后端处理时,发生异常后,向前端抛出,前端也进行处理,完成异常的显示。

后端异常拦截部分:

1
2
3
4
5
6
7
8
9
10
11
//全局异常拦截
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler
fun handlerException(exception: Exception): SaResult? {
if ( exception is NotLoginException ) {
return SaResult.code(501)
}
return SaResult.error(exception.message)
}
}

注意,默认返回code为500,不登录返回code为501,服务器寄了返回code为502

现在,前端Response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export const request: RequestConfig = {
timeout: 20000,
errorConfig: {
errorHandler: (error) => {
//responseInterceptors捕捉不到
// @ts-ignore
if (error.code === 'ERR_BAD_RESPONSE') {
message.error('服务器寄了');
return;
}
message.error(error.message);
},
},
responseInterceptors: [
(response) => {
// @ts-ignore
const {
data: { code, msg },
} = response;
switch (code) {
case 500:
throw new Error(msg);
case 501:
message.error('未登录账号');
history.push('/login');
break;
}
return response;
},
],
};

errorHandler用来拦截异常,responseInterceptors来监视Response。

nginx的502报错直接发给errorHandler


现在,该写token部分了:

我们采用SaToken框架,这个嘎嘎好用√

只要将SaTokenConfigure类暴露在容器中,就可以完成配置啦:

1
2
3
4
5
6
7
@Component
class SaTokenConfigure: WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(SaAnnotationInterceptor())
.addPathPatterns("/**")
}
}

在这里,我们启动了注解功能,可以校验是否登录。


然后再来写application层(应用层)与apis(接口层):

建立LoginService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Service
class LoginService {
@Autowired
private lateinit var accountRepository: AccountRepository

fun register(userName: String, password: String) {
val timestamp = Instant.now()
var account = Account()
account.userId = timestamp.toEpochMilli().toString() + (100..999).random().toString()
account.userName = userName
if ( accountRepository.checkingUserName(userName) ) {
throw Exception("用户名重复")
}
account.password = BCrypt.hashpw(password, BCrypt.gensalt())
account.role = "user"
account.deleted = 0
try {
accountRepository.insertAccount(account)
} catch (e: Exception) {
throw Exception("注册失败")
}
}

fun login(userName: String, password: String) {
val user = accountRepository.selectAccountByName(userName)
?: throw Exception("未查找到该用户")
if (BCrypt.checkpw(password, user.password)) {
StpUtil.login(user.userId)
//录入user的缓存
StpUtil.getSession().set("user", user)
} else {
throw Exception("密码错误")
}
}

fun logout() {
StpUtil.logout()
}
}
  • 先来说注册方法,我们采用时间戳来作id(因为没用户,时间戳就够了XD),然后查重用户名,重要的是
    将前端发来的密码加密,采用BCrypt类提供的方法加密存入数据
    ,最后将数据插入数据库就好啦。

建立LoginController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//提供进出系统的接口
@RestController
class LoginController {
@Autowired
private lateinit var loginService: LoginService

data class LoginReq(
var userName: String,
var password: String
)

@PostMapping("/register")
fun signIn(@RequestBody req: LoginReq) {
loginService.register(req.userName, req.password)
}

@PostMapping("/login")
fun loginIn(@RequestBody req: LoginReq) {
loginService.login(req.userName, req.password)
}

@SaCheckLogin
@PostMapping("/logout")
fun loginOut(): String {
loginService.logout()
return "登出"
}

@PostMapping("/isLogin")
fun isLogin(): String {
return if (loginService.isLogin()) "在" else "不在"
}

}

这里提供四个api,注册,登录,退出,判断在线。接口层主要关注传来参数与交付给应用层,所以这里直接调用应用层就好啦


咳咳,一页写不下前端页面部分了,就不写了XD,大致就是写个页面加服务就行啦!
这样比较简陋的论坛进出系统就写好了。