Envío de correo programado en otra fecha en Java

Iniciado por olimpico10, 4 Octubre 2018, 20:55 PM

0 Miembros y 1 Visitante están viendo este tema.

olimpico10

Buenas tardes,

estoy desarrollando una aplicación web con Java en el framework Struts2. Necesito que, a los X dias de realizar una acción en mi programa, se envíe un correo recordatorio a una dirección de email especifica de forma automática. Se como puedo enviar correos usando la API JAVA MAIL, pero no se si existe algún servicio o alguna cosa que pueda utilizar para programar el envío en una fecha concreta del futuro.

Agradezco vuestra ayuda de antemano, un saludo.

3n31ch

Hola amigo, eso comúnmente se programa aparte como parte de una tarea automática realiza por el SO. Puedes leer al respecto a continuación:

https://es.wikipedia.org/wiki/Cron_(Unix)

Por lo regular se programa un cron para que cada 24 horas acceda a una URL de tu servicio web (en este caso Java), que a su vez, verifique la base de datos y vea a quienes hay que enviarle correos, posterior a esto los envié.

Si haces esto, hay dos cosas que tienes que tener en consideración:
1. Crea un token de seguridad que impida que un usuario común pueda acceder a dicha URL.
2. Guarda un registro que impida que la query se ejecute 2 veces en un mismo día, por error.

La razón de estas dos recomendaciones surge a partir de una falla de seguridad que tenia wordpress que permitía que un usuario común accediera a la URL encargada de enviar correos de felicitaciones a usuarios que estuvieran de cumpleaños. Esta URL era llamada mediante un cron, y por tanto si tu accedías 1000 veces, se enviaban 1000 correos.

olimpico10

Hola 3n31ch, gracias por tu respuesta, imaginaba que sería así, era por confirmar. Me pondré a ello a ver como puedo hacerlo. Tengo que ver como programar en Struts 2 una URL de este tipo, y controlar la seguridad. ¿Podrias darme alguna directriz al respecto si conoces el framework? Gracias de antemano.

Por cierto, buscando por ahí encontre lo siguiente:

http://www.quartz-scheduler.org/

Es una librería que, al parecer, sirve para postergar la ejecución de código, lo que no tengo ni idea es de como funcionará, ya que entiendo que por supuesto esto debe ser el SO quien lo realice, no se como interactuará la librería con el. ¿Que te parece la librería a simple vista? ¿La conoces?

Un saludo.

3n31ch

Lo lamento, mas de lo que ya te he dicho no te puedo ayudar.

PD: Puede ser una URL como también puede ser un programa en java de consola. Da igual..

rub'n

#4
Hola, que tal, Structs 2  >:( porque no Springframework :D ? pero bueno que mas da

bueno mira, usa


https://mvnrepository.com/artifact/org.quartz-scheduler/quartz/2.3.0

Código (xml) [Seleccionar]
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
   <groupId>org.quartz-scheduler</groupId>
   <artifactId>quartz</artifactId>
   <version>2.3.0</version>
</dependency>


Código (xml) [Seleccionar]
<!-- https://mvnrepository.com/artifact/javax.mail/javax.mail-api -->
<dependency>
   <groupId>javax.mail</groupId>
   <artifactId>javax.mail-api</artifactId>
   <version>1.6.2</version>
</dependency>



Esto te ayuda a crear la expresion de cron http://www.cronmaker.com/



creas una clase que implente a Job, en ese metodo execute manda el mail

Código (java) [Seleccionar]
public class TimerClass implements Job {

   public TimerClass() {

   }

   @Override
   public void execute(JobExecutionContext jobExecutionContext) {
        //Logica del mail aqui
         try {
              System.out.println("Hola");
         }catch(JobExecutionException  ex) {
         }
       
   }
}


En tu clase que implementa el ServletContextListener dentro del método

Código (java) [Seleccionar]
public void contextInitialized(ServletContextEvent param){
new ExecTimer();
}


Código (java) [Seleccionar]
public class ExecTimer {

   private JobDetail job;
   private Trigger trigger;
   private Scheduler scheduler;

   private static final String TWENTY_SEC = "0/20 * * * * ?"; //cada 20 segundos

                                     //seg,min,hora,dia,mes,diaSemana,Año(Opcional)
                                             //todos los viernes a las 6:30AM
   private static final String FECHA_FUTURA = "00 30 6 ? * FRI";   // http://www.cronmaker.com/


   public ExecTimer() {
       initTask();
   }

   private void initTask() {
       try {
           job = JobBuilder.newJob(TimerClass.class)
                   .withIdentity("EnviarMail")
                   .build();
           trigger = TriggerBuilder
                   .newTrigger()
                   .withSchedule(CronScheduleBuilder.cronSchedule(FECHA_FUTURA))
                   .build();
           scheduler = new StdSchedulerFactory().getScheduler();
           scheduler.start();
           scheduler.scheduleJob(job, trigger);
       } catch (SchedulerException e) {
           e.printStackTrace();
       }
   }
}


Con Springboot seria mas facil aun, menos codigo y sin .xml  >:D

Código (java) [Seleccionar]
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class Application {

   public static void main(String ...blabla) {
       SpringApplication.run(Application.class);
   }

}



Código (java) [Seleccionar]
 
@Scheduled(cron= "00 30 6 ? * FRI")
public void enviarMail() {
   System.out.println("Hola, ");
}


Y otra cosa importante la operación de envío del mail puedes ejecutarla de manera asíncrona, hay mucha maneras de hacerlo con java, hará que tu aplicación no se bloquee, mas reactiva  :D


rubn0x52.com KNOWLEDGE  SHOULD BE FREE!!!
If you don't have time to read, you don't have the time (or the tools) to write, Simple as that. Stephen

olimpico10

Buenas rub'n ,

estoy conociendo ahora Structs 2, y la verdad es que me está gustando como framework, Springframework no lo he trabajado aun.

En cuanto a tus indicaciones, muchas gracias, lo intentaré así. En cualquier caso, sabes como funciona exactamente Quartz internamente? Me refiero a como programa los trabajos. (Esto último es solo curiosidad  :laugh:)

Un saludo.

rub'n

Cita de: olimpico10 en  7 Octubre 2018, 17:36 PM
Buenas rub'n ,

estoy conociendo ahora Structs 2, y la verdad es que me está gustando como framework, Springframework no lo he trabajado aun.

En cuanto a tus indicaciones, muchas gracias, lo intentaré así. En cualquier caso, sabes como funciona exactamente Quartz internamente? Me refiero a como programa los trabajos. (Esto último es solo curiosidad  :laugh:)

Un saludo.

Pues desde luego que no, guíate por su API  ;D


rubn0x52.com KNOWLEDGE  SHOULD BE FREE!!!
If you don't have time to read, you don't have the time (or the tools) to write, Simple as that. Stephen

olimpico10

Cita de: rub'n en  5 Octubre 2018, 04:38 AM
Hola, que tal, Structs 2  >:( porque no Springframework :D ? pero bueno que mas da

bueno mira, usa


https://mvnrepository.com/artifact/org.quartz-scheduler/quartz/2.3.0

Código (xml) [Seleccionar]
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
   <groupId>org.quartz-scheduler</groupId>
   <artifactId>quartz</artifactId>
   <version>2.3.0</version>
</dependency>


Código (xml) [Seleccionar]
<!-- https://mvnrepository.com/artifact/javax.mail/javax.mail-api -->
<dependency>
   <groupId>javax.mail</groupId>
   <artifactId>javax.mail-api</artifactId>
   <version>1.6.2</version>
</dependency>



Esto te ayuda a crear la expresion de cron http://www.cronmaker.com/



creas una clase que implente a Job, en ese metodo execute manda el mail

Código (java) [Seleccionar]
public class TimerClass implements Job {

   public TimerClass() {

   }

   @Override
   public void execute(JobExecutionContext jobExecutionContext) {
        //Logica del mail aqui
         try {
              System.out.println("Hola");
         }catch(JobExecutionException  ex) {
         }
       
   }
}


En tu clase que implementa el ServletContextListener dentro del método

Código (java) [Seleccionar]
public void contextInitialized(ServletContextEvent param){
new ExecTimer();
}


Código (java) [Seleccionar]
public class ExecTimer {

   private JobDetail job;
   private Trigger trigger;
   private Scheduler scheduler;

   private static final String TWENTY_SEC = "0/20 * * * * ?"; //cada 20 segundos

                                     //seg,min,hora,dia,mes,diaSemana,Año(Opcional)
                                             //todos los viernes a las 6:30AM
   private static final String FECHA_FUTURA = "00 30 6 ? * FRI";   // http://www.cronmaker.com/


   public ExecTimer() {
       initTask();
   }

   private void initTask() {
       try {
           job = JobBuilder.newJob(TimerClass.class)
                   .withIdentity("EnviarMail")
                   .build();
           trigger = TriggerBuilder
                   .newTrigger()
                   .withSchedule(CronScheduleBuilder.cronSchedule(FECHA_FUTURA))
                   .build();
           scheduler = new StdSchedulerFactory().getScheduler();
           scheduler.start();
           scheduler.scheduleJob(job, trigger);
       } catch (SchedulerException e) {
           e.printStackTrace();
       }
   }
}


Con Springboot seria mas facil aun, menos codigo y sin .xml  >:D

Código (java) [Seleccionar]
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class Application {

   public static void main(String ...blabla) {
       SpringApplication.run(Application.class);
   }

}



Código (java) [Seleccionar]
 
@Scheduled(cron= "00 30 6 ? * FRI")
public void enviarMail() {
   System.out.println("Hola, ");
}


Y otra cosa importante la operación de envío del mail puedes ejecutarla de manera asíncrona, hay mucha maneras de hacerlo con java, hará que tu aplicación no se bloquee, mas reactiva  :D

Buenas rub'n, estoy implementando ahora esto que me comentaste, pero olvidé preguntarte algo importante que no comprendo.

¿Como puedo hacer para programar la tarea del envío del correo un mes despues de que un usuario realice un evento (alta en base de datos) ? Es decir, que en cuanto el usuario realice el evento, se programe la tarea.

Lo que no veo es como llamar desde la clase que gestiona el alta en base de datos a execTimer para pasarle la fecha en la que se debe enviar el correo.

De nuevo gracias por tu ayuda.

rub'n

#8
Eso se puede ajustar agregando un método set o por el constructor del  

Código (java) [Seleccionar]

private String fecha;
public ExecTimer(final String fecha) {
   this.fecha = fecha;
   initTask();
}


O con el método set

Código (java) [Seleccionar]

public void setFecha(final String fecha) {
   this.fecha = fecha;
}


Con guava event bus también sirve, dependiendo que tan acoplado este tu código  

Esta fecha futura es la que debemos de setear por lo antes nombrado

Código (java) [Seleccionar]
private static final String FECHA_FUTURA = "00 30 6 ? * FRI";

tu comentas

CitarLo que no veo es como llamar desde la clase que gestiona el alta en base de datos a execTimer para pasarle la fecha en la que se debe enviar el correo.

justo en esa parte debería ver un trozo del código para ver que tal esta, recuerda que el método execute del TimerClass es donde debe ir tu logica cuando el usuario dispare algun evento

ver el entrypoint de tu aplicación es también importante.




Entonces refactorizando un poco crearemos nuestro builder para personalizar un poco homie, que se lo setearemos al método setTimeBean de la clase ExecTimer  :xD

el método toString le hacemos override para adaptarlo a nuestras necesidades  >:D, tanto day como month son requeridos sino la misma API de quartz atrapara el error en runtime XD

el string retornado es la expresión de cron


Código (java) [Seleccionar]
   @Override
   public String toString() {
       final StringBuilder stringBuilder = new StringBuilder()
               .append(sec)
               .append(" ")
               .append(min)
               .append(" ")
               .append(hour)
               .append(" ")
               .append(day) // requeridp
               .append(" ")
               .append(month) // requerido
               .append(" ")
               .append("?");// opcional el YEAR XD
       System.out.println("Expresion cron: "+stringBuilder.toString());
       return stringBuilder.toString();



Código (java) [Seleccionar]

package foro;

public final class TimeBean {

   private final int sec;
   private final int min;
   private final int hour;
   private final int day;
   private final int month;

   private TimeBean(final Builder builder) {
       this.sec = builder.sec;
       this.min = builder.min;
       this.hour = builder.hour;
       this.day = builder.day;
       this.month = builder.month;
   }

   public int getSec() {
       return sec;
   }

   public int getMin() {
       return min;
   }

   public int getHour() {
       return hour;
   }

   public int getDay() {
       return day;
   }

   public int getMonth() {
       return month;
   }

   @Override
   public String toString() {
       final StringBuilder stringBuilder = new StringBuilder()
               .append(sec)
               .append(" ")
               .append(min)
               .append(" ")
               .append(hour)
               .append(" ")
               .append(day)
               .append(" ")
               .append(month)
               .append(" ")
               .append("?");// opcional el YEAR XD
       System.out.println("Expresion cron: "+stringBuilder.toString());
       return stringBuilder.toString();
   }

  public static class Builder {
       private int sec;
       private int min;
       private int hour;
       private int day;
       private int month;

       public Builder withSec(final int sec) {
           this.sec = sec;
           return this;
       }
       public Builder withMin(final int min) {
           this.min = min;
           return this;
       }
       public Builder withHour(final int hour) {
           this.hour = hour;
           return this;
       }
       public Builder withDay(final int day) {
           this.day = day;
           return this;
       }
       public Builder withMonth(final int month) {
           this.month = month;
           return this;
       }
       public TimeBean build() {
           return new TimeBean(this);
       }
   }



Un ligero cambio a la clase ExecTimer

Código (java) [Seleccionar]

package foro;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Objects;

public class ExecTimer  {

   private JobDetail job;
   private Trigger trigger;
   private Scheduler scheduler;

   private TimeBean timeBean;

   public ExecTimer() {

   }

   public ExecTimer(final TimeBean timeBean) {
       this.timeBean = Objects.requireNonNull(timeBean,()->"TimeBean sin instanciar crear builder");
   }

   public void setTimeBean(final TimeBean timeBean) {
       this.timeBean = Objects.requireNonNull(timeBean,()->"TimeBean sin instanciar crear builder");
   }

   public void initTask() {
       try {

           job = JobBuilder.newJob(TimerClass.class)
                   .withIdentity("EnviarMail")
                   .build();

           trigger = TriggerBuilder.newTrigger()
                   .withIdentity("testing v1.1 para olimpico10", "group1")
                   .withSchedule(CronScheduleBuilder.cronSchedule(timeBean.toString()))
                   .build();

           scheduler = new StdSchedulerFactory().getScheduler();
           scheduler.start();
           scheduler.scheduleJob(job, trigger);

       } catch (SchedulerException e) {
           e.printStackTrace();
       }
   }
}



Aquí esta clase ejecutara el ExecTimer a modo de ejemplo, tu puedes crear la tuya en tu proyecto para instanciar
Vemos 2 clases nuevas aquí ambas geniales con la nueva API de java.time



Código (java) [Seleccionar]

private final ZoneId zoneId = ZoneId.of("Europe/Madrid");
private final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.now(),zoneId).plusSeconds(5);



Este método añade 5 segundos despues de el actual al String final o mejor dicho a la Expresion cron homie


Código (java) [Seleccionar]
.plusSeconds(5);

Código (java) [Seleccionar]

package foro;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ExcuteTaskOnNext {

   private ExecTimer execTimer = new ExecTimer();
   private final ZoneId zoneId = ZoneId.of("Europe/Madrid");
   private final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.now(),zoneId).plusSeconds(5);

   public ExcuteTaskOnNext() {
       init();
   }

   private void init() {

      final TimeBean timeBean = new TimeBean.Builder()
               .withSec(zonedDateTime.getSecond())
               .withMin(zonedDateTime.getMinute())
               .withHour(zonedDateTime.getHour())
               .withDay(zonedDateTime.getDayOfMonth())//el dia es requerido
               .withMonth(zonedDateTime.getMonthValue()) //el mes es requrido
               .build();

       execTimer.setTimeBean(timeBean);
       execTimer.initTask();
   }

   public static void main(String ...agaga) {
       new ExcuteTaskOnNext();
   }
}



Entonces vamos a lo siguiente tu me diras joderrr tiiioo, pero este chaval le digo mil veces que quiero mandar el fucking correo al mes siguiente  :xD

solo te tocara añadir esto  :xD


Código (java) [Seleccionar]
plusMonths(1)


nuestro builder podemos configurarlo de la siguente manera, o sea, valido también, si unicamente vas a usar el envio mensual


Código (java) [Seleccionar]

//para crear una tarea mensual el builder es valido asi segun cronmaker
       final TimeBean timeBean = new TimeBean.Builder()
               .withDay(zonedDateTime.getDayOfMonth())//el dia es requerido
               .withMonth(zonedDateTime.getMonthValue()) //el mes es requrido
               .build();



si colocamos la expresion originada gracias a la linea 54 de la clase TimeBean Expresion cron: 0 0 0 25 11 ?

en http://www.cronmaker.com/ tenemos



consta que debería crear el proyecto en struc2  :xD pero me da flojera quizás mañana, tu tienes que probar aunque sea mandando ese correo a los 5 segundos, pero no se como es tu código ahí





La clase ExcuteTaskOnNext a sido renombrada  RunNextTask , siendo una mejor versión

Código (java) [Seleccionar]
package com.example.javamail.executarnextmonth;

import com.example.javamail.util.GenericBuilder;
import com.example.javamail.util.ShowData;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.ui.Label;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

@SpringComponent
public class RunNextTask implements ShowData {

   @Autowired
   private ExecTimer execTimer;
   private ZonedDateTime zonedDateTime;
   private TimeBean timeBean;


   public void initTimeBuilder(final Tiempos tiposTiempos, final Integer valueTime, Label label) {
       try {
           switch (tiposTiempos) {
               case SEGUNDOS:
                   zonedDateTime = getZoneDateTime().plusSeconds(valueTime);
                   timeBean = getTimeBeanBuilder();
                   break;
               case MINUTOS:
                   zonedDateTime = getZoneDateTime().plusMinutes(valueTime);
                   timeBean = getTimeBeanBuilder();
                   break;
               case HORAS:
                   zonedDateTime = getZoneDateTime().plusHours(valueTime);
                   timeBean = getTimeBeanBuilder();
                   break;
               case MESES:
                   zonedDateTime = getZoneDateTime().plusMonths(valueTime);
                   timeBean = getTimeBeanBuilder();
                   break;
           }
       }catch (Exception ex){
           getLogger().error(null,ex);
       }
       execTimer.setTimeBean(timeBean);
       label.setValue("Expresión cron "+timeBean.toString());
       execTimer.initTask();
   }

   /**
    * Metodo de uso opcional
    */
   public void initTimeBeanMonth() {
       final ZoneId zoneId = ZoneId.of("Europe/Madrid");
       final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.now(),zoneId).plusMonths(1);
       //para crear una tarea mensual el builder es valido asi segun cronmaker

       final TimeBean timeBean = GenericBuilder.of(TimeBean::new)
               .with(TimeBean::setDay,zonedDateTime.getDayOfMonth())
               .with(TimeBean::setMonth,zonedDateTime.getMonthValue())
               .build();

       execTimer.setTimeBean(timeBean);
       execTimer.initTask();
   }

   private ZonedDateTime getZoneDateTime() {
       return ZonedDateTime.ofInstant(Instant.now(),ZoneId.of("Europe/Madrid"));
   }

   private TimeBean getTimeBeanBuilder() {
       return GenericBuilder.of(TimeBean::new)
               .with(TimeBean::setSec,this.zonedDateTime.getSecond())
               .with(TimeBean::setMin,this.zonedDateTime.getMinute())
               .with(TimeBean::setHour,this.zonedDateTime.getHour())
               .with(TimeBean::setDay,this.zonedDateTime.getDayOfMonth())
               .with(TimeBean::setMonth,this.zonedDateTime.getMonthValue())
               .build();
   }
}



donde invocaremos al método initTimeBuilder, con 3 parámetros


  • El tipo de tiempo
  • El retardo
  • El label para mostrar la salida

Código (java) [Seleccionar]
@Autowired
private RunNextTask runNextTask;


Código (java) [Seleccionar]
runNextTask.initTimeBuilder(tiempo, valueTime, labelExpresionCron);

eso estará contenido en la clase FormEmailSender.java

> Opción guiarse por aquí

https://foro.elhacker.net/java/envio_de_emails_programados_con_springboot_vaadin_8_quartz-t489066.0.html


rubn0x52.com KNOWLEDGE  SHOULD BE FREE!!!
If you don't have time to read, you don't have the time (or the tools) to write, Simple as that. Stephen