在Spring MVC框架下利用RESTful API和MongoDB实现用户留言与邮件反馈

在Spring MVC框架下,基于注解映射和数据绑定编写Java业务代码,采用MongoDB数据库进行数据存储,使用JSP和JS渲染表单页面,利用RESTful API实现基于URL的请求处理服务,以实现简单的用户留言与邮件反馈功能。通过IDEA IDE工具搭建好Spring MVC框架并连接MongoDB数据库后,即可进行该功能的实现。

Spring MVC运行流程

执行步骤

  1. 客户端请求提交到DispatcherServlet;
  2. 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller;
  3. DispatcherServlet将请求提交到Controller;
  4. Controller调用业务逻辑处理后,返回ModelAndView;
  5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图;
  6. 视图负责将结果显示到客户端。

执行流程图

这里写图片描述

Domain与Dto对象

Domain对象

/**
 * UserFeedback.java
 * 
 * @author Zero
 */

//@Document>>将domain依赖注入MongoDB Repository
@Document(collection = "UserFeedback")
//UserFeedback类通过AbstractDomain实现了java.io.Serializable接口,其实例可安全的将数据保存到HttpSession中
public class UserFeedback extends AbstractDomain {

    private static final long serialVersionUID = 3843558621545635010L;

    //姓名
    private String fullName;
    //职位
    private String position;
    //电话
    private String phone;

    //留言类型
    private FeedbackType contentType;
    //留言内容
    private String content;
    //留言状态
    private FeedbackStatus status;

    public UserFeedback() {
    }

    public String fullName() {
        return fullName;
    }

    public UserFeedback fullName(String fullName) {
        this.fullName = fullName;
        return this;
    }

    //此处省略其他属性的get/set方法

    @Override
    public String toString() {
        return "UserFeedback{" +
                "fullName='" + fullName + '\'' +
                ", position='" + position + '\'' +
                ", phone='" + phone + '\'' +
                ", contentType='" + contentType + '\'' +
                ", content='" + content + '\'' +
                ", status=" + status +
                '}';
    }
}

DTO对象

/**
 * UserFeedbackFormDto.java
 * 
 * @author Zero
 */
public class UserFeedbackFormDto extends AbstractDto {

    private static final long serialVersionUID = -5003694127258885488L;

    //姓名
    private String fullName;
    //职位
    private String position;
    //电话
    private String phone;

    //留言类型
    private FeedbackType contentType;
    //留言内容
    private String content;

    private String captcha;

    public UserFeedbackFormDto() {
    }

    public UserFeedbackFormDto(UserFeedback userfeedback) {
        super(userfeedback);
        this.fullName = userfeedback.fullName();
        this.position = userfeedback.position();
        this.phone = userfeedback.phone();
        this.contentType = userfeedback.contentType();
        this.content = userfeedback.content();

    }

    public UserFeedbackFormDto(FeedbackType feedbackType) {
        this.contentType = feedbackType;
    }

    public FeedbackType[] getFeedbackTypes(){
        return FeedbackType.values();
    }

    //此处省略其他属性set/get方法

    public static List<UserFeedbackFormDto> toDtos(List<UserFeedback> list) {
        List<UserFeedbackFormDto> dtos = new ArrayList<>(list.size());
        dtos.addAll(list.stream().map(UserFeedbackFormDto::new).collect(Collectors.toList()));
        return dtos;
    }
}

基于注解的控制器

Controller类

使用一个基于注解的控制器可以处理多个动作,利用Dispatcher Servlet实现请求转发,利用RESTful API实现基于URL的请求处理服务,通过调用封装了后端复杂逻辑的Servlet类实现业务逻辑处理。而且使用@RequestingMapping注解类型即可对一个方法进行请求处理,其请求映射不需要存储在配置文件中。控制器部分的代码如下:

/**
 * UserFeedbackEmailController.java
 * 留言&&反馈
 *
 * @author Zero
 */

//@Controller>>标识类是一个hander处理器
@Controller
//@RequestingMapping>>注解控制器类时则所有的方法都将映射为类级别的请求,value属性将URL映射到方法
@RequestMapping("/contact/")
public class UserFeedbackEmailController {

    //@Autowired>>service实例注入controller,
    @Autowired
    private UserFeedbackService userFeedbackService;

    //@RequestingMapping>>标识方法时则为一个请求处理方法,method属性指示该方法仅处理的http类型,返回值解析为跳转路径
    @RequestMapping(value = "feedback_form", method = RequestMethod.POST)
    //@ModelAttribute>>前端请求参数绑定到formDto入参,formDto对象添加到model,返回类型为string时则为逻辑视图名
    public String sendFeedbackEmail(@ModelAttribute("formDto") @Valid UserFeedbackFormDto formDto, BindingResult result, Model model, HttpServletRequest request) {

        //检查captcha
        final String correctCaptcha = WebUtils.getCaptchaKey(request.getSession());
        final String captcha = formDto.getCaptcha();
        if (StringUtils.isEmpty(captcha) || !captcha.equalsIgnoreCase(correctCaptcha)) {
            result.rejectValue("captcha", null, "验证码错误");
            return "front/contact";
        }

        ////@Valid>>validator验证器,判断bindingResult.hasErrors(),有误则返回dto对应Validator定制消息提示
        if (result.hasErrors()) {
            return "front/contact";
        }

        //service实现类调用sendFeedbackEmail方法,以实现具体的业务处理
        String contactResult = userFeedbackService.sendFeedbackEmail(formDto);
        //创建model实例,传数据到前端
        model.addAttribute("contactResult", contactResult);
        //redirect经过client,避免user重新加载页面时再调用相同action
        return "redirect:../contact";

    }
}

Validator类

控制器中使用了JSR 303 验证器,通过注解给对象属性添加约束以及定制错误消息。提交表单中的数据传输对象UserFeedbackFormDto对应的验证器代码如下:

/**
 * UserFeedbackFormDtoValidator.java
 * 验证表单对象
 * 
 * @author Zero
 */

//@Component>>把普通pojo实例化到spring容器,这里把validator注入spring容器中
@Component
//validator接口:validator编程实现数据验证的接口
public class UserFeedbackFormDtoValidator implements Validator {

    @Override
    //supports方法:判断当前验证器是否支持指定的clazz验证,如果支持返回true
    public boolean supports(Class<?> clazz) {
        return UserFeedbackFormDto.class.equals(clazz);
    }

    @Override
    //validate方法:验证目标对象target,将验证错误填入包含一系列FieldError和ObjectError对象的Errors对象
    //Errors对象中的错误信息,可利用表单标签库的Errors标签显示在HTML页面,错误消息可通过Spring支持的国际化特性进行本地化
    public void validate(Object target, Errors errors) {
        //给Errors对象添加错误的方法:在Errors对象上调用一个reject或rejectValue方法
        //ValidationUtils是一个有助于编写Validator的工具类
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "fullName", null, "姓名不能为空!");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "position", null, "职位不能为空!");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "contentType", null, "留言类型为选择!");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "content", null, "留言内容不能为空!");

        UserFeedbackFormDto formDto = (UserFeedbackFormDto) target;
        validatePhoneNumber(formDto, errors);
    }

    private void validatePhoneNumber(UserFeedbackFormDto formDto, Errors errors) {
        final String phoneNumber = formDto.getPhone();
        if (StringUtils.isEmpty(phoneNumber)) {
            errors.rejectValue("phone", null, "电话号码不能为空!");
            return;
        }
        if (!MatchUtils.isPhoneNumber(phoneNumber)) {
            errors.rejectValue("phone", null, "电话号码格式错误!");
        }

    }
}

业务逻辑实现

Service接口与实现类

/**
 * UserFeedbackService.java
 * 
 * @author Zero
 */
public interface UserFeedbackService {
    String sendFeedbackEmail(UserFeedbackFormDto formDto);
}
/**
 * UserFeedbackServiceImpl.java
 * 
 * @author Zero
 */
@Service("userFeedbackService")
public class UserFeedbackServiceImpl implements UserFeedbackService {

    @Override
    public String sendFeedbackEmail(UserFeedbackFormDto formDto) {
        //创建handler对象,调用其hander方法处理具体业务
        SendFeedbackEmailHandler handler = new SendFeedbackEmailHandler(formDto);
        return handler.handle();

    }
}

Handler业务处理类

/**
 * SendFeedbackEmailHandler.java
 * 
 * @author Zero
 */
public class SendFeedbackEmailHandler {

    private static final Logger LOG = LoggerFactory.getLogger(SendFeedbackEmailHandler.class);

    private static final String SUCCESS_RESULT = "1";
    private static final String SUBJECT = "有新的待处理留言信息";
    //TEMPLATE为反馈邮件发送模板
    private static final String TEMPLATE = "template/zh_feedback_root_notice.html";

    private transient UserFeedbackRepository userFeedbackRepository = BeanProvider.getBean(UserFeedbackRepository.class);

    private transient MailRepository mailRepository = BeanProvider.getBean(MailRepository.class);

    private UserFeedbackFormDto formDto;

    public SendFeedbackEmailHandler(UserFeedbackFormDto formDto) {
        this.formDto = formDto;
    }

    public String handle() {
        //将留言写入数据库
        UserFeedback userFeedback = newUserFeedback();
        userFeedbackRepository.saveUserFeedback(userFeedback);
        //记录日志
        AuditLog.create("添加留言(fullName = " + userFeedback.fullName() + ", uuid = " + userFeedback.uuid() + ")");
        LOG.debug("{}|Save Feedback : {}", WebUtils.getIp(), userFeedback);
        //邮件反馈
        sendNoticeEmailToRoot(userFeedback);
        return SUCCESS_RESULT;

    }

    private UserFeedback newUserFeedback() {
        return new UserFeedback()
                .fullName(formDto.getFullName())
                .position(formDto.getPosition())
                .phone(formDto.getPhone())
                .contentType(formDto.getContentType())
                .content(formDto.getContent());
    }

    //发送邮件的具体实现
    protected void sendNoticeEmailToRoot(UserFeedback userFeedback) {
        final IDPConfiguration idpConfiguration = IDPHolder.idpConfiguration();

        //rootEmailAddress
        List<String> emails = idpConfiguration.rootEmailList();
        if (null == emails || emails.size() < 1) {
            LOG.debug("{}| Not Found EmailAddress: {}", SecurityUtils.username(), emails);
            return;
        }
        Map<String, Object> params = new HashMap<>();
        params.put("fullName", userFeedback.fullName());
        params.put("position", userFeedback.position());
        params.put("phone", userFeedback.phone());
        params.put("contentType", userFeedback.contentType().getLabel());
        params.put("content", userFeedback.content());
        params.put("hostUrl", Holder.host() + "login");

        STRender render = new STRender(TEMPLATE, params);
        String content = render.render();
        for (String email : emails) {
            //MailTransmitter为邮件发送器
            MailTransmitter mailTransmitter = new MailTransmitter().subject(SUBJECT).to(email).content(content);
            final MailTransmitResult result = mailRepository.sendMail(mailTransmitter);
            AuditLog.createMailLog(email, SUBJECT + "{发送结果:" + result + "}", "来自 " + userFeedback.fullName() + " 的" + userFeedback.contentType().getLabel() + "留言信息需要处理");
        }

    }
}

数据持久层

Repository接口

/**
 * UserFeedbackRepository.java
 * 
 * @author Zero
 */
public interface UserFeedbackRepository {
    void saveUserFeedback(UserFeedback userfeedback);

    List<UserFeedback> findUserFeedbackList(Map<String,Object> map);

    long totalUserFeedbackList(Map<String,Object> map);
}

Repository实现类

/**
 * UserFeedbackRepositoryMongoDB.java
 * 
 * @author Zero
 */
@Repository("userFeedbackRepository")
public class UserFeedbackRepositoryMongoDB  extends AbstractMongoSupport implements UserFeedbackRepository {
    @Override
    public void saveUserFeedback(UserFeedback userfeedback) {
        this.mongoTemplate().save(userfeedback);
    }

    @Override
    public List<UserFeedback> findUserFeedbackList(Map<String, Object> map) {
        Query query = addPagination(new Query(),map);
        addFeedbackConditions(query,map);
        return this.mongoTemplate().find(query,UserFeedback.class);
    }

    private void addFeedbackConditions(Query query, Map<String, Object> map) {
        String enterpriseName = (String)map.get("enterpriseName");
        String content = (String)map.get("content");
        if(StringUtils.isNotEmpty(enterpriseName)){
            addRegexCriteria(query,"enterpriseName",enterpriseName);
        }
        if(StringUtils.isNotEmpty(content)){
            addRegexCriteria(query,"content",content);
        }
        query.with(new Sort(new Sort.Order[]{new Sort.Order(Sort.Direction.DESC, "createTime")}));
    }

    @Override
    public long totalUserFeedbackList(Map<String, Object> map) {
        Query query = addPagination(new Query(),map);
        addFeedbackConditions(query,map);
        return this.mongoTemplate().count(query, UserFeedback.class);
    }
}

前端页面渲染

JSP页面表单

//contast.jsp 
<form:form commandName="formDto" action="${contextPath}/contact/feedback_form" cssClass="pubform ess-form">
                <input type="hidden" name="forward" value="${contextPath}/contact">
                <%--<tags:csrf/>--%>

                <div class="input-box">
                    <form:input path="fullName" placeholder="请输入姓名" required="true"/>
                    <form:errors path="fullName" cssClass="label text-danger"/>
                </div>
                <div class="input-box">
                    <form:input path="position" placeholder="请输入职位" required="true"/>
                    <form:errors path="position" cssClass="label text-danger"/>
                </div>
                <div class="input-box">
                    <form:input path="phone" placeholder="请输入手机号码" required="true"/>
                    <span class="err errphone text-danger" style="display: block;"><form:errors path="phone"/></span>
                </div>

                <div class="input-box">
                    <form:select path="contentType" required="true">
                        <form:options items="${formDto.feedbackTypes}" itemLabel="label"/>
                    </form:select>
                    <form:errors path="contentType" cssClass="label text-danger"/>
                </div>

                <div class="input-box tex-box">
                    <form:textarea path="content" placeholder="请输入留言" required="true"/>
                    <form:errors path="content" cssClass="label text-danger"/>
                </div>

                <div class="input-group input-box idencode-box">
                    <form:input path="captcha" onkeyup="value=value.replace(/[^\w\.\/]/ig,'')" name="captcha"
                            class="form-control" maxlength="4" placeholder="请输入验证码" autocomplete="off" required="true"/>
                                    <span class="input-group-addon input-sm">
                                        <img src="${pageContext.request.contextPath}/public/captcha.pngx" 
                                                onclick="this.src = this.src+'?'" alt="Captcha" style="margin: -2px;"/>
                                    </span>
                    <span class="err" style="display: block;"><form:errors path="captcha" cssClass="label text-danger"/></span>
                </div>

                <div class="input-box sub-box"><input type="submit" name="submit" id="submit" value="提交"/>
                </div>
                <c:if test="${param.contactResult eq '1'}">
                    <div class="success-box" id="connectSuccess">
                        <div class="bg"><span class="gou"></span></div>
                        <div class="tit-success">提交成功</div>
                        <p class="des-success">感谢您的关注。</p>
                        <p class="tit-success" style="font-size: 16px"><span id="closeTime">3</span>秒后自动关闭</p>
                    </div>
                </c:if>
            </form:form>

JS页面渲染

//contact.js
var Contact = {

    init: function () {
        this.evBind();
    },
    evBind: function () {
        var self = this;
        self.connectSuccessHide();

        $('#phone').on('blur',function(){
            self.testTel($(this).val(),$('.errphone'))
        })

    },
    testTel: function (val, err) {
        var flag = false;
        if (!this.checkIdentity(val)) {
            err.html('请输入正确格式的手机号');
            flag=true;
        } else {
            flag=false;
            err.html('');
        }
        return flag
    },
    checkIdentity: function (val) {
        return (/^1[34578]\d{9}$/.test(val));
    },
    connectSuccessHide:function(){
        var time = 3;
        var closeTimer =  setInterval(function(){
            time--;
            if(time==0){

                clearInterval(closeTimer);
                $('#connectSuccess').slideUp(400)
            }
            $('#closeTime').html(time);
        },1000)
    }

}

$(function(){

    Contact.init();
})

猜你喜欢

转载自blog.csdn.net/weixin_37325825/article/details/74245943