Skip to content

Commit

Permalink
feat: 支持邮箱登录
Browse files Browse the repository at this point in the history
在个人中心-安全设置中绑定邮箱后,才支持邮箱登录
  • Loading branch information
Charles7c committed Oct 24, 2023
1 parent d9af44f commit 17b169e
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package top.charles7c.cnadmin.auth.model.request;

import java.io.Serializable;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import lombok.Data;

import io.swagger.v3.oas.annotations.media.Schema;

import org.hibernate.validator.constraints.Length;

import top.charles7c.cnadmin.common.constant.RegexConsts;

/**
* 邮箱登录信息
*
* @author Charles7c
* @since 2023/10/23 20:15
*/
@Data
@Schema(description = "邮箱登录信息")
public class EmailLoginRequest implements Serializable {

private static final long serialVersionUID = 1L;

/**
* 邮箱
*/
@Schema(description = "邮箱", example = "123456789@qq.com")
@NotBlank(message = "邮箱不能为空")
@Pattern(regexp = RegexConsts.EMAIL, message = "邮箱格式错误")
private String email;

/**
* 验证码
*/
@Schema(description = "验证码", example = "888888")
@NotBlank(message = "验证码不能为空")
@Length(max = 6, message = "验证码非法")
private String captcha;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,24 @@
public interface LoginService {

/**
* 用户登录
* 账号登录
*
* @param username
* 用户名
* @param password
* 密码
* @return 令牌
*/
String login(String username, String password);
String accountLogin(String username, String password);

/**
* 邮箱登录
*
* @param email
* 邮箱
* @return 令牌
*/
String emailLogin(String email);

/**
* 三方账号登录
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,19 @@ public class LoginServiceImpl implements LoginService {
private final UserSocialService userSocialService;

@Override
public String login(String username, String password) {
public String accountLogin(String username, String password) {
UserDO user = userService.getByUsername(username);
CheckUtils.throwIfNull(user, "用户名或密码错误");
CheckUtils.throwIfNull(user, "用户名或密码不正确");
Long userId = user.getId();
CheckUtils.throwIfNotEqual(SecureUtils.md5Salt(password, userId.toString()), user.getPassword(), "用户名或密码错误");
CheckUtils.throwIfNotEqual(SecureUtils.md5Salt(password, userId.toString()), user.getPassword(), "用户名或密码不正确");
this.checkUserStatus(user);
return this.login(user);
}

@Override
public String emailLogin(String email) {
UserDO user = userService.getByEmail(email);
CheckUtils.throwIfNull(user, "此邮箱未绑定本系统账号");
this.checkUserStatus(user);
return this.login(user);
}
Expand Down Expand Up @@ -187,6 +195,6 @@ private String login(UserDO user) {
private void checkUserStatus(UserDO user) {
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, user.getStatus(), "此账号已被禁用,如有疑问,请联系管理员");
DeptDetailVO deptDetailVO = deptService.get(user.getDeptId());
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, deptDetailVO.getStatus(), "此账号部门已被禁用,如有疑问,请联系管理员");
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, deptDetailVO.getStatus(), "此账号所属部门已被禁用,如有疑问,请联系管理员");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ public interface UserMapper extends DataPermissionMapper<UserDO> {
@Select("SELECT * FROM `sys_user` WHERE `username` = #{username}")
UserDO selectByUsername(@Param("username") String username);

/**
* 根据邮箱查询
*
* @param email
* 邮箱
* @return 用户信息
*/
@Select("SELECT * FROM `sys_user` WHERE `email` = #{email}")
UserDO selectByEmail(@Param("email") String email);

/**
* 根据 ID 查询昵称
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ public interface UserService extends BaseService<UserVO, UserDetailVO, UserQuery
*/
UserDO getByUsername(String username);

/**
* 根据邮箱查询
*
* @param email
* 邮箱
* @return 用户信息
*/
UserDO getByEmail(String email);

/**
* 根据部门 ID 列表查询
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ public UserDO getByUsername(String username) {
return baseMapper.selectByUsername(username);
}

@Override
public UserDO getByEmail(String email) {
return baseMapper.selectByEmail(email);
}

@Override
public Long countByDeptIds(List<Long> deptIds) {
return baseMapper.lambdaQuery().in(UserDO::getDeptId, deptIds).count();
Expand Down
18 changes: 13 additions & 5 deletions continew-admin-ui/src/api/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const BASE_URL = '/auth';

export interface LoginReq {
phone?: string;
email?: string;
username?: string;
password?: string;
captcha: string;
Expand All @@ -17,8 +16,17 @@ export interface LoginRes {
token: string;
}

export function login(req: LoginReq) {
return axios.post<LoginRes>(`${BASE_URL}/login`, req);
export function accountLogin(req: LoginReq) {
return axios.post<LoginRes>(`${BASE_URL}/account`, req);
}

export interface EmailLoginReq {
email: string;
captcha: string;
}

export function emailLogin(req: EmailLoginReq) {
return axios.post<LoginRes>(`${BASE_URL}/email`, req);
}

export function logout() {
Expand All @@ -34,9 +42,9 @@ export function listRoute() {
}

export function socialAuth(source: string) {
return axios.get<string>(`${BASE_URL}/${source}`);
return axios.get<string>(`/oauth/${source}`);
}

export function socialLogin(source: string, req: any) {
return axios.post<LoginRes>(`${BASE_URL}/${source}`, req);
return axios.post<LoginRes>(`/oauth/${source}`, req);
}
23 changes: 18 additions & 5 deletions continew-admin-ui/src/store/modules/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { defineStore } from 'pinia';
import {
login as userLogin,
LoginReq,
EmailLoginReq,
accountLogin as userAccountLogin,
emailLogin as userEmailLogin,
socialLogin as userSocialLogin,
logout as userLogout,
getUserInfo,
LoginReq,
} from '@/api/auth/login';
import { getImageCaptcha as getCaptcha } from '@/api/common/captcha';
import { setToken, clearToken } from '@/utils/auth';
Expand Down Expand Up @@ -42,10 +44,21 @@ const useUserStore = defineStore('user', {
return getCaptcha();
},

// 用户登录
async login(req: LoginReq) {
// 账号登录
async accountLogin(req: LoginReq) {
try {
const res = await userAccountLogin(req);
setToken(res.data.token);
} catch (err) {
clearToken();
throw err;
}
},

// 邮箱登录
async emailLogin(req: EmailLoginReq) {
try {
const res = await userLogin(req);
const res = await userEmailLogin(req);
setToken(res.data.token);
} catch (err) {
clearToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
if (!errors) {
loading.value = true;
userStore
.login({
.accountLogin({
username: values.username,
password: encryptByRsa(values.password) || '',
captcha: values.captcha,
Expand Down
98 changes: 78 additions & 20 deletions continew-admin-ui/src/views/login/components/email-login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
layout="vertical"
size="large"
class="login-form"
@submit="handleLogin"
>
<a-form-item field="email" hide-label>
<a-input
Expand All @@ -32,19 +33,23 @@
</a-button>
</a-form-item>
<a-button class="btn" :loading="loading" type="primary" html-type="submit"
>{{ $t('login.button') }}(即将开放)
>{{ $t('login.button') }}
</a-button>
</a-form>
</template>

<script lang="ts" setup>
import { getCurrentInstance, ref, toRefs, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { ValidatedError } from '@arco-design/web-vue';
import { useUserStore } from '@/store';
import { LoginReq } from '@/api/auth/login';
import { EmailLoginReq } from '@/api/auth/login';
import { getMailCaptcha } from '@/api/common/captcha';
const { proxy } = getCurrentInstance() as any;
const { t } = useI18n();
const router = useRouter();
const userStore = useUserStore();
const loading = ref(false);
const captchaLoading = ref(false);
Expand All @@ -54,7 +59,10 @@
const captchaBtnNameKey = ref('login.captcha.get');
const captchaBtnName = computed(() => t(captchaBtnNameKey.value));
const data = reactive({
form: {} as LoginReq,
form: {
email: '',
captcha: '',
} as EmailLoginReq,
rules: {
email: [
{ required: true, message: t('login.email.error.required.email') },
Expand Down Expand Up @@ -85,26 +93,76 @@
if (!valid) {
captchaLoading.value = true;
captchaBtnNameKey.value = 'login.captcha.ing';
captchaLoading.value = false;
captchaDisable.value = true;
captchaBtnNameKey.value = `${t(
'login.captcha.get'
)}(${(captchaTime.value -= 1)}s)`;
captchaTimer.value = window.setInterval(() => {
captchaTime.value -= 1;
captchaBtnNameKey.value = `${t('login.captcha.get')}(${
captchaTime.value
}s)`;
if (captchaTime.value < 0) {
window.clearInterval(captchaTimer.value);
captchaTime.value = 60;
captchaBtnNameKey.value = t('login.captcha.get');
captchaDisable.value = false;
}
}, 1000);
getMailCaptcha({
email: form.value.email,
})
.then((res) => {
captchaLoading.value = false;
captchaDisable.value = true;
captchaBtnNameKey.value = `${t(
'login.captcha.get'
)}(${(captchaTime.value -= 1)}s)`;
captchaTimer.value = window.setInterval(() => {
captchaTime.value -= 1;
captchaBtnNameKey.value = `${t('login.captcha.get')}(${
captchaTime.value
}s)`;
if (captchaTime.value <= 0) {
window.clearInterval(captchaTimer.value);
captchaTime.value = 60;
captchaBtnNameKey.value = t('login.captcha.get');
captchaDisable.value = false;
}
}, 1000);
proxy.$message.success(res.msg);
})
.catch(() => {
resetCaptcha();
captchaLoading.value = false;
});
}
});
};
/**
* 登录
*
* @param errors 表单验证错误
* @param values 表单数据
*/
const handleLogin = ({
errors,
values,
}: {
errors: Record<string, ValidatedError> | undefined;
values: Record<string, any>;
}) => {
if (loading.value) return;
if (!errors) {
loading.value = true;
userStore
.emailLogin({
email: values.email,
captcha: values.captcha,
})
.then(() => {
const { redirect, ...othersQuery } = router.currentRoute.value.query;
router.push({
name: (redirect as string) || 'Workplace',
query: {
...othersQuery,
},
});
proxy.$notification.success(t('login.success'));
})
.catch(() => {
form.value.captcha = '';
})
.finally(() => {
loading.value = false;
});
}
};
</script>

<style lang="less" scoped>
Expand Down
Loading

0 comments on commit 17b169e

Please sign in to comment.