diff --git a/pom.xml b/pom.xml index e034cf4a..8dd94d4d 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,10 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-mail + org.springframework.boot spring-boot-starter-security diff --git a/src/main/java/cn/tomoya/module/code/dao/CodeDao.java b/src/main/java/cn/tomoya/module/code/dao/CodeDao.java new file mode 100644 index 00000000..36c7fc2e --- /dev/null +++ b/src/main/java/cn/tomoya/module/code/dao/CodeDao.java @@ -0,0 +1,17 @@ +package cn.tomoya.module.code.dao; + +import cn.tomoya.module.code.entity.Code; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * Created by tomoya on 17-6-6. + */ +@Repository +public interface CodeDao extends JpaRepository { + + List findByCodeAndType(String code, String type); + +} diff --git a/src/main/java/cn/tomoya/module/code/entity/Code.java b/src/main/java/cn/tomoya/module/code/entity/Code.java new file mode 100644 index 00000000..00860bb7 --- /dev/null +++ b/src/main/java/cn/tomoya/module/code/entity/Code.java @@ -0,0 +1,67 @@ +package cn.tomoya.module.code.entity; + +import javax.persistence.*; +import java.util.Date; + +/** + * Created by tomoya on 17-6-6. + */ +@Entity +@Table(name = "pybbs_code") +public class Code { + + @Id + @GeneratedValue + private int id; + + @Column(unique = true) + private String code; + + @Column(name = "expire_time") + private Date expireTime; + + private String type; + + @Column(name = "is_used") + private boolean isUsed; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Date getExpireTime() { + return expireTime; + } + + public void setExpireTime(Date expireTime) { + this.expireTime = expireTime; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean isUsed() { + return isUsed; + } + + public void setUsed(boolean used) { + isUsed = used; + } +} diff --git a/src/main/java/cn/tomoya/module/code/entity/CodeEnum.java b/src/main/java/cn/tomoya/module/code/entity/CodeEnum.java new file mode 100644 index 00000000..0c6a7112 --- /dev/null +++ b/src/main/java/cn/tomoya/module/code/entity/CodeEnum.java @@ -0,0 +1,11 @@ +package cn.tomoya.module.code.entity; + +/** + * Created by tomoya on 17-6-6. + */ +public enum CodeEnum { + + EMAIL, + SMS + +} diff --git a/src/main/java/cn/tomoya/module/code/service/CodeService.java b/src/main/java/cn/tomoya/module/code/service/CodeService.java new file mode 100644 index 00000000..2661ffb5 --- /dev/null +++ b/src/main/java/cn/tomoya/module/code/service/CodeService.java @@ -0,0 +1,59 @@ +package cn.tomoya.module.code.service; + +import cn.tomoya.module.code.dao.CodeDao; +import cn.tomoya.module.code.entity.Code; +import cn.tomoya.module.code.entity.CodeEnum; +import cn.tomoya.util.DateUtil; +import cn.tomoya.util.StrUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; + +/** + * Created by tomoya on 17-6-6. + */ +@Service +@Transactional +public class CodeService { + + @Autowired + private CodeDao codeDao; + + public Code findByCodeAndType(String code, CodeEnum type) { + List codes = codeDao.findByCodeAndType(code, type.name()); + if(codes.size() > 0) return codes.get(0); + return null; + } + + public void save(Code code) { + codeDao.save(code); + } + + public String genEmailCode() { + String genCode = StrUtil.randomString(6); + Code code = findByCodeAndType(genCode, CodeEnum.EMAIL); + if(code != null) { + return genEmailCode(); + } else { + code = new Code(); + code.setCode(genCode); + code.setExpireTime(DateUtil.getMinuteAfter(new Date(), 10)); + code.setType(CodeEnum.EMAIL.name()); + code.setUsed(false); + save(code); + return genCode; + } + } + + public int validateCode(String code, CodeEnum type) { + Code code1 = findByCodeAndType(code, type); + if(code1 == null) return 1;// 验证码不正确 + if(DateUtil.isExpire(code1.getExpireTime())) return 2; // 过期了 + code1.setUsed(true); + save(code1); + return 0; // 正常 + } +} diff --git a/src/main/java/cn/tomoya/module/index/controller/IndexController.java b/src/main/java/cn/tomoya/module/index/controller/IndexController.java index 91fccdc4..afbdaf71 100644 --- a/src/main/java/cn/tomoya/module/index/controller/IndexController.java +++ b/src/main/java/cn/tomoya/module/index/controller/IndexController.java @@ -2,7 +2,11 @@ import cn.tomoya.common.BaseController; import cn.tomoya.common.config.SiteConfig; +import cn.tomoya.exception.ApiException; import cn.tomoya.exception.Result; +import cn.tomoya.module.code.entity.Code; +import cn.tomoya.module.code.entity.CodeEnum; +import cn.tomoya.module.code.service.CodeService; import cn.tomoya.module.topic.entity.Topic; import cn.tomoya.module.topic.service.TopicService; import cn.tomoya.module.user.entity.User; @@ -10,9 +14,14 @@ import cn.tomoya.util.FileUploadEnum; import cn.tomoya.util.FileUtil; import cn.tomoya.util.PageWrapper; +import cn.tomoya.util.StrUtil; import cn.tomoya.util.identicon.Identicon; +import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.data.domain.Page; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -40,6 +49,8 @@ @Controller public class IndexController extends BaseController { + private Logger log = Logger.getLogger(IndexController.class); + @Autowired private TopicService topicService; @Autowired @@ -50,6 +61,12 @@ public class IndexController extends BaseController { private Identicon identicon; @Autowired private FileUtil fileUtil; + @Autowired + private JavaMailSender javaMailSender; + @Autowired + private CodeService codeService; + @Autowired + private Environment env; /** * 首页 @@ -75,6 +92,7 @@ public String index(String tab, Integer p, Model model) { /** * 搜索 + * * @param p * @param q * @param model @@ -123,30 +141,57 @@ public String toRegister(HttpServletResponse response) { * @return */ @PostMapping("/register") - public String register(String username, String password, HttpServletResponse response, Model model) { + public String register(String username, String password, String email, String emailCode, String code, + HttpSession session, HttpServletResponse response, Model model) { + String genCaptcha = (String) session.getAttribute("index_code"); + if (StringUtils.isEmpty(code)) { + model.addAttribute("errors", "验证码不能为空"); + return render("/register"); + } + if (!genCaptcha.toLowerCase().equals(code.toLowerCase())) { + model.addAttribute("errors", "验证码错误"); + return render("/register"); + } User user = userService.findByUsername(username); if (user != null) { model.addAttribute("errors", "用户名已经被注册"); - } else if (StringUtils.isEmpty(username)) { + return render("/register"); + } + if (StringUtils.isEmpty(username)) { model.addAttribute("errors", "用户名不能为空"); - } else if (StringUtils.isEmpty(password)) { + return render("/register"); + } + if (StringUtils.isEmpty(password)) { model.addAttribute("errors", "密码不能为空"); - } else { - Date now = new Date(); - String avatarName = UUID.randomUUID().toString(); - identicon.generator(avatarName); - user = new User(); - user.setUsername(username); - user.setPassword(new BCryptPasswordEncoder().encode(password)); - user.setInTime(now); - user.setBlock(false); - user.setToken(UUID.randomUUID().toString()); - user.setAvatar(siteConfig.getStaticUrl() + "avatar/" + avatarName + ".png"); - user.setAttempts(0); - userService.save(user); - return redirect(response, "/login?s=reg"); + return render("/register"); } - return render("/register"); + User user_email = userService.findByEmail(email); + if (user_email != null) { + model.addAttribute("errors", "邮箱已经被使用"); + return render("/register"); + } + int validateResult = codeService.validateCode(emailCode, CodeEnum.EMAIL); + if(validateResult == 1) { + model.addAttribute("errors", "邮箱验证码不正确"); + return render("/register"); + } + if(validateResult == 2) { + model.addAttribute("errors", "邮箱验证码已过期"); + return render("/register"); + } + Date now = new Date(); + String avatarName = UUID.randomUUID().toString(); + identicon.generator(avatarName); + user = new User(); + user.setUsername(username); + user.setPassword(new BCryptPasswordEncoder().encode(password)); + user.setInTime(now); + user.setBlock(false); + user.setToken(UUID.randomUUID().toString()); + user.setAvatar(siteConfig.getStaticUrl() + "avatar/" + avatarName + ".png"); + user.setAttempts(0); + userService.save(user); + return redirect(response, "/login?s=reg"); } /** @@ -192,6 +237,7 @@ public String wangEditorUpload(@RequestParam("file") MultipartFile file) { /** * 关于 + * * @param model * @return */ @@ -207,11 +253,12 @@ public String about(Model model) { private int xx = 22; private int fontHeight = 26; private int codeY = 25; - char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', - 'V', 'W', 'X', 'Y', '3', '4', '5', '6', '7', '8' }; + char[] codeSequence = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', '3', '4', '5', '6', '7', '8'}; /** * 验证码生成 + * * @param req * @param resp * @throws IOException @@ -281,4 +328,24 @@ public void getCode(HttpServletRequest req, HttpServletResponse resp) throws IOE sos.close(); } + @GetMapping("/sendEmailCode") + @ResponseBody + public Result sendEmailCode(String email) throws ApiException { + if (!StrUtil.isEmail(email)) throw new ApiException("请输入正确的Email"); + try { + String genCode = codeService.genEmailCode(); + SimpleMailMessage message = new SimpleMailMessage(); + System.out.println(env.getProperty("spring.mail.username")); + message.setFrom(env.getProperty("spring.mail.username")); + message.setTo("1956587218@qq.com"); + message.setSubject("注册验证码 - " + siteConfig.getName()); + message.setText("你的验证码为: " + genCode + " , 请在10分钟内使用!"); + javaMailSender.send(message); + return Result.success(); + } catch (Exception e) { + log.error(e.getMessage()); + return Result.error("邮件发送失败"); + } + } + } diff --git a/src/main/java/cn/tomoya/module/security/core/ValidateCodeAuthenticationFilter.java b/src/main/java/cn/tomoya/module/security/core/ValidateCodeAuthenticationFilter.java index 724c57c7..39c3e08e 100644 --- a/src/main/java/cn/tomoya/module/security/core/ValidateCodeAuthenticationFilter.java +++ b/src/main/java/cn/tomoya/module/security/core/ValidateCodeAuthenticationFilter.java @@ -48,7 +48,6 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ throw new AuthenticationServiceException("不支持非POST方式的请求!"); // 验证码验证 String requestCaptcha = request.getParameter("code"); - log.info(requestCaptcha); String genCaptcha = (String) request.getSession().getAttribute("index_code"); log.info(genCaptcha); if (StringUtils.isEmpty(requestCaptcha)) diff --git a/src/main/java/cn/tomoya/module/user/dao/UserDao.java b/src/main/java/cn/tomoya/module/user/dao/UserDao.java index d10dfc9a..4a22f3f9 100644 --- a/src/main/java/cn/tomoya/module/user/dao/UserDao.java +++ b/src/main/java/cn/tomoya/module/user/dao/UserDao.java @@ -16,16 +16,18 @@ @CacheConfig(cacheNames = "users") public interface UserDao extends JpaRepository { - @Cacheable - User findOne(int id); + @Cacheable + User findOne(int id); - @Cacheable - User findByUsername(String username); + @Cacheable + User findByUsername(String username); - @CacheEvict - void delete(int id); + @CacheEvict + void delete(int id); - @Cacheable - User findByToken(String token); + @Cacheable + User findByToken(String token); + @Cacheable + User findByEmail(String email); } diff --git a/src/main/java/cn/tomoya/module/user/entity/User.java b/src/main/java/cn/tomoya/module/user/entity/User.java index b5969a63..ca8e7c34 100644 --- a/src/main/java/cn/tomoya/module/user/entity/User.java +++ b/src/main/java/cn/tomoya/module/user/entity/User.java @@ -51,6 +51,7 @@ public class User extends BaseEntity implements Serializable { private String avatar; // 用户邮箱 + @Column(nullable = false, unique = true) private String email; // 个人签名 diff --git a/src/main/java/cn/tomoya/module/user/service/UserService.java b/src/main/java/cn/tomoya/module/user/service/UserService.java index 2adf9b00..45e51469 100644 --- a/src/main/java/cn/tomoya/module/user/service/UserService.java +++ b/src/main/java/cn/tomoya/module/user/service/UserService.java @@ -23,87 +23,94 @@ @Transactional public class UserService { - @Autowired - private UserDao userDao; - @Autowired - private TopicService topicService; - @Autowired - private ReplyService replyService; - @Autowired - private NotificationService notificationService; - @Autowired - private CollectService collectService; + @Autowired + private UserDao userDao; + @Autowired + private TopicService topicService; + @Autowired + private ReplyService replyService; + @Autowired + private NotificationService notificationService; + @Autowired + private CollectService collectService; - public User findById(int id) { - return userDao.findOne(id); - } + public User findById(int id) { + return userDao.findOne(id); + } - /** - * 根据用户名判断是否存在 - * - * @param username - * @return - */ - public User findByUsername(String username) { - return userDao.findByUsername(username); - } + /** + * 根据用户名判断是否存在 + * + * @param username + * @return + */ + public User findByUsername(String username) { + return userDao.findByUsername(username); + } - public void save(User user) { - userDao.save(user); - } + public User findByEmail(String email) { + return userDao.findByEmail(email); + } - public void updateUser(User user) { - userDao.save(user); - } + public void save(User user) { + userDao.save(user); + } - /** - * 分页查询用户列表 - * - * @param p - * @param size - * @return - */ - public Page pageUser(int p, int size) { - Sort sort = new Sort(new Sort.Order(Sort.Direction.DESC, "inTime")); - Pageable pageable = new PageRequest(p - 1, size, sort); - return userDao.findAll(pageable); - } + public void updateUser(User user) { + userDao.save(user); + } - /** - * 禁用用户 - * @param id - */ - public void blockUser(Integer id) { - User user = findById(id); - user.setBlock(true); - updateUser(user); - } + /** + * 分页查询用户列表 + * + * @param p + * @param size + * @return + */ + public Page pageUser(int p, int size) { + Sort sort = new Sort(new Sort.Order(Sort.Direction.DESC, "inTime")); + Pageable pageable = new PageRequest(p - 1, size, sort); + return userDao.findAll(pageable); + } - /** - * 用户解禁 - * @param id - */ - public void unBlockUser(Integer id) { - User user = findById(id); - user.setBlock(false); - updateUser(user); - } + /** + * 禁用用户 + * + * @param id + */ + public void blockUser(Integer id) { + User user = findById(id); + user.setBlock(true); + updateUser(user); + } - /** - * 根据令牌查询用户 - * @param token - * @return - */ - public User findByToken(String token) { - return userDao.findByToken(token); - } + /** + * 用户解禁 + * + * @param id + */ + public void unBlockUser(Integer id) { + User user = findById(id); + user.setBlock(false); + updateUser(user); + } - /** - * 删除用户 - * 注:这会删除用户的所有记录,慎重操作 - * @param id - */ - //TODO 关联太多,不提供删除用户操作 + /** + * 根据令牌查询用户 + * + * @param token + * @return + */ + public User findByToken(String token) { + return userDao.findByToken(token); + } + + /** + * 删除用户 + * 注:这会删除用户的所有记录,慎重操作 + * @param id + */ + //TODO 关联太多,不提供删除用户操作 // public void deleteById(int id) { // User user = findById(id); // //删除用户的收藏 diff --git a/src/main/java/cn/tomoya/util/DateUtil.java b/src/main/java/cn/tomoya/util/DateUtil.java new file mode 100644 index 00000000..888a918a --- /dev/null +++ b/src/main/java/cn/tomoya/util/DateUtil.java @@ -0,0 +1,116 @@ +package cn.tomoya.util; + +import org.springframework.util.StringUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * Created by tomoya. + * Copyright (c) 2016, All Rights Reserved. + * http://tomoya.cn + */ +public class DateUtil { + + public static final String FORMAT_DATETIME = "yyyy-MM-dd HH:mm:ss"; + public static final String FORMAT_DATE = "yyyy-MM-dd"; + + public static String formatDateTime(Date date) { + if (date == null) return null; + SimpleDateFormat sdf = new SimpleDateFormat(FORMAT_DATETIME); + sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")); + return sdf.format(date); + } + + public static String formatDate(Date date) { + if (date == null) return null; + SimpleDateFormat sdf = new SimpleDateFormat(FORMAT_DATE); + sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")); + return sdf.format(date); + } + + public static String formatDateTime(Date date, String style) { + if (date == null) return null; + SimpleDateFormat sdf = new SimpleDateFormat(style); + sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")); + return sdf.format(date); + } + + /** + * 字符串转时间 + * + * @param dateString + * @param style + * @return + */ + public static Date string2Date(String dateString, String style) { + if (StringUtils.isEmpty(dateString)) return null; + Date date = new Date(); + SimpleDateFormat strToDate = new SimpleDateFormat(style); + try { + date = strToDate.parse(dateString); + } catch (ParseException e) { + e.printStackTrace(); + } + return date; + } + + /** + * 判断传入的时间是否在当前时间之后,返回boolean值 + * true: 过期 + * false: 还没过期 + * + * @param date + * @return + */ + public static boolean isExpire(Date date) { + if (date.before(new Date())) return true; + return false; + } + + public static Date getHourAfter(Date date, int hour) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR, hour + 1); + return calendar.getTime(); + } + + public static Date getHourBefore(Date date, int hour) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR, -(hour - 1)); + return calendar.getTime(); + } + + public static Date getDateBefore(Date date, int day) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.DAY_OF_MONTH, -day); + return calendar.getTime(); + } + + public static Date getDateAfter(Date date, int day) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.DAY_OF_MONTH, day); + return calendar.getTime(); + } + + public static Date getMinuteAfter(Date date, int minute) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.MINUTE, minute); + return calendar.getTime(); + } + + public static Date getMinuteBefore(Date date, int minute) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.MINUTE, -minute); + return calendar.getTime(); + } + +} \ No newline at end of file diff --git a/src/main/java/cn/tomoya/util/StrUtil.java b/src/main/java/cn/tomoya/util/StrUtil.java index e2e2a695..85258725 100644 --- a/src/main/java/cn/tomoya/util/StrUtil.java +++ b/src/main/java/cn/tomoya/util/StrUtil.java @@ -4,6 +4,8 @@ import java.util.Random; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Created by tomoya. @@ -16,6 +18,17 @@ public class StrUtil { 'e', 'f' }; static final char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; static final Random random = new Random(); + static final String check = "^([a-z0-9A-Z]+[-|_|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; + + public static boolean isEmail(String email) { + if (StringUtils.isEmpty(email)) { + return false; + } else { + Pattern pattern = Pattern.compile(check); + Matcher matcher = pattern.matcher(email); + return matcher.matches(); + } + } /** * 随机指定长度的字符串 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 63779d82..afb2525f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -18,6 +18,10 @@ spring: ddl-auto: update mvc: static-path-pattern: /static/** + mail: + host: smtp.qq.com + username: xxoo@qq.com + password: # 如果是qq邮箱的话,这里要配置成授权码 site: name: 朋也社区 intro:
属于Java语言的bbs

在这里,您可以提问,回答,分享,诉说,这是个属于Java程序员的社区,欢迎您的加入!

@@ -29,4 +33,4 @@ site: editor: wangeditor search: true attempts: 5 - attemptsWaitTime: 15 \ No newline at end of file + attemptsWaitTime: 15 diff --git a/src/main/resources/templates/default/login.ftl b/src/main/resources/templates/default/login.ftl index d08ea054..16899bb7 100644 --- a/src/main/resources/templates/default/login.ftl +++ b/src/main/resources/templates/default/login.ftl @@ -23,15 +23,13 @@ -
- -
-
- -
-
+
+ +
+ + -
+
diff --git a/src/main/resources/templates/default/register.ftl b/src/main/resources/templates/default/register.ftl index 3c10fb40..15c2d6a0 100644 --- a/src/main/resources/templates/default/register.ftl +++ b/src/main/resources/templates/default/register.ftl @@ -10,7 +10,7 @@ <#if errors??>
${errors!}
-
+
@@ -20,7 +20,26 @@
+
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+
@@ -31,15 +50,42 @@ $("#form").submit(function () { var username = $("#username").val(); var password = $("#password").val(); - if(username.length == 0) { - layui.msg("用户名不能为空", {icon: 2}); + if(username.length === 0) { + $("#error_message").text("用户名不能为空"); return false; } - if(password.length == 0) { - layui.msg("密码不能为空", {icon: 2}); + if(password.length === 0) { + $("#error_message").text("密码不能为空"); return false; } - }) + }); + $("#changeCode").click(function(){ + var date = new Date(); + $(this).attr("src", "/code?ver=" + date.getTime()); + }); + $("#send_email_btn").click(function () { + $("#send_email_btn").attr("disabled", true); + $.ajax({ + url: "/sendEmailCode", + async: false, + cache: false, + type: 'get', + dataType: "json", + data: { + type: 'reg', + email: $("#reg_email").val() + }, + success: function (data) { + if (data.code === 200) { + $("#send_email_btn").html("发送成功"); + $("#reg_email").attr("disabled", true); + } else { + $("#error_message").text(data.description); + $("#send_email_btn").attr("disabled", false); + } + } + }); + }); }) \ No newline at end of file