Spring mail 郵件服務及其參數配置(properties文件的讀寫)

一個Web 系統通常會少不了郵件服務的,比如用於註冊,密碼找回,訂單提醒等應用場景。Spring 封裝了一個簡單易用的關於郵件發送的工具類JavaMailSenderImpl 。

系統要提供郵件服務,那得需要一個郵件服務器,用於發送和回覆郵件。如果有條件專門弄一個郵件服務器那固然是最好的,但是也可以簡單的使用163或者qq提供的郵件服務。

例如註冊了一個[email protected]的郵箱賬號,在 設置 勾選 POP3/SMTP服務,然後保存。點擊左側導航欄中的 客戶端授權密碼 ,開啓客戶端授權碼,重置授權碼,你會收到一個授權碼的短信,這個授權碼就是用來第三方客戶端登錄的密碼,這個待會會使用到。

1.核心工具類MailUtil.java

import java.util.Date;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.core.io.Resource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;

public class MailUtil {
    public static JavaMailSenderImpl mailSender = createMailSender(ConfigInfo.mail_host,ConfigInfo.mail_port,ConfigInfo.mail_username,
            ConfigInfo.mail_password,ConfigInfo.mail_smtp_timeout);
    public static String mailFrom = ConfigInfo.mail_from;
    private static JavaMailSenderImpl createMailSender(String host,int port,String username,String password,int timeout){
        JavaMailSenderImpl sender = new JavaMailSenderImpl();
        sender.setHost(host);
        sender.setPort(port);
        sender.setUsername(username);
        sender.setPassword(password);
        sender.setDefaultEncoding("Utf-8");
        Properties p = new Properties();
        p.setProperty("mail.smtp.timeout",timeout+"");
        p.setProperty("mail.smtp.auth","true");
        sender.setJavaMailProperties(p);
        return sender;
    }
    //發送測試的郵件
    public static void sendMailForTest(String host,int port,String username,String password,String from,
            String to){
        SimpleMailMessage mail = new SimpleMailMessage();
        mail.setFrom(from);
        mail.setTo(to);
        mail.setSubject("這是測試郵件,請勿回覆!");
        mail.setSentDate(new Date());// 郵件發送時間
        mail.setText("這是一封測試郵件。如果您已收到此郵件,說明您的郵件服務器已設置成功。請勿回覆,請勿回覆,請勿回覆,重要的事說三遍!");
        JavaMailSenderImpl sender = createMailSender(host,port,username,password,25000);
        sender.send(mail);
    }
    public static void sendTextMail(String to,String subject,String text){
        SimpleMailMessage mail = new SimpleMailMessage();
        mail.setFrom(mailFrom);
        mail.setTo(to);
        mail.setSubject(subject);
        mail.setSentDate(new Date());// 郵件發送時間
        mail.setText(text);
        mailSender.send(mail);
    }
    public static void sendHtmlMail(String to,String subject,String html) throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        // 設置utf-8或GBK編碼,否則郵件會有亂碼
        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        messageHelper.setFrom(mailFrom);
        messageHelper.setTo(to);
        messageHelper.setSubject(subject);
        messageHelper.setText(html, true);
        mailSender.send(mimeMessage);
    }

    public static void sendFileMail(String to,String subject,String html,String contentId,Resource resource) throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        // 設置utf-8或GBK編碼,否則郵件會有亂碼
        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        messageHelper.setFrom(mailFrom);
        messageHelper.setTo(to);
        messageHelper.setSubject(subject);
        messageHelper.setText(html, true);
        //FileSystemResource img = new FileSystemResource(new File("c:/350.jpg"));
        messageHelper.addInline(contentId, resource);
        // 發送
        mailSender.send(mimeMessage);
    }
}

爲了使用方便,採用靜態方法的實現方式,其中的JavaMailSenderImpl 實例是通過代碼的方式創建的,脫離了Spring容器的管理。當然也可以使用Spring注入的方式:

<bean id="propertyConfigurer"  
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
        <property name="location" value="classpath:config.properties" />  
    </bean> 
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="smtp.163.com" />
        <property name="port" value="25" />
        <property name="username" value="${mail_username}"
        <property name="password" value="${mail_password}" />
        <property name="defaultEncoding" value="UTF-8"></property>  
        <property name="javaMailProperties">
            <props>
                <prop key="mail.smtp.auth">true</prop>
                <prop key="mail.smtp.timeout">25000</prop>
            </props>
        </property>
    </bean>

在代碼中直接這樣注入:

//1)首先類需要用 @Component 註解類,並要配置掃描此包,讓Spring識別到。
//2)然後JavaMailSender實例通過@Autowired來自動裝配,其他不變
//3)需要在Spring容器中配置屬性文件PropertyPlaceholderConfigurer
public static JavaMailSender mailSender;
@Autowired
public void setMailSender(JavaMailSender mailSender){
    MailUtil.mailSender = mailSender;
}

2.config.properties

mail_host=smtp.163.com
mail_port=25
mail_username=example@163.com
mail_password=NiDongDeShouQuanMa
mail_smtp_timeout=25000
mail_from=example@163.com

3.ConfigInfo.java (與上面的屬性文件對應)

package com.jykj.demo.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;

public class ConfigInfo {
    public static final String PROPERTIES_DEFAULT = "config.properties";//類路徑下的屬性文件名
    //mail
    public static String mail_host;
    public static int mail_port;
    public static String mail_from;
    public static String mail_username;
    public static String mail_password;
    public static int mail_smtp_timeout;
    static{
        initOrRefresh();
    }
    //初始化或更新緩存
    public static void initOrRefresh(){
        Properties p=new Properties();
        try {
            InputStream in = ConfigInfo.class.getClassLoader().getResourceAsStream(PROPERTIES_FILE_PATH);
            p.load(in);
            in.close();
            mail_host = p.getProperty("mail_host","smtp.163.com");
            mail_port = Integer.parseInt(p.getProperty("mail_port","25"));
            mail_from = p.getProperty("mail_from");
            mail_username = p.getProperty("mail_username");
            mail_password = p.getProperty("mail_password");
            mail_smtp_timeout = Integer.parseInt(p.getProperty("mail_smtp_timeout","25000"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

現在有這樣個需求:需要一個參數配置界面來配置郵件服務器,裏面的參數如主機、端口,賬號等等都可以更改的。爲了實現它,需要對config.properties文件進行讀寫操作,這樣就不能用Spring注入JavaMailSender 實例的方式,因爲Spring容器在初始化時只會加載.properties文件一次,運行時修改了屬性文件,需要重啓應用才能生效,這顯然是不合理的,不能重啓應用所以只能採用java的對properties操作的API,把它看成普通文件進行讀寫操作,更改完後重新加載屬性文件即可,這樣讓config.properties脫離Spring容器管理。

mail設置

郵件服務登錄密碼 就是之前提到的 授權碼。
發送測試郵件 : 將通過發送賬號[email protected][email protected]發送一封測試的郵件,收到了則表示該配置成功。

接下來實現保存配置參數的功能,這主要是對屬性文件的讀寫操作。

4.ConfigUtil.java(屬性文件的讀寫)

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

//屬性文件 的讀寫
public class ConfigUtil {   
    public static String getProperty(String key) throws IOException {
        return getProperty(ConfigInfo.PROPERTIES_DEFAULT,key);
    }
    public static Object setProperty(String propertyName,String propertyValue) throws URISyntaxException, IOException {
        return setProperty(ConfigInfo.PROPERTIES_DEFAULT,propertyName,propertyValue);
    }
    public static void setProperties(Set<Entry<String, Object>> data) throws IOException, URISyntaxException{
        setProperties(ConfigInfo.PROPERTIES_DEFAULT,data);
    }
    // 讀取Properties的全部信息
    public static Map<String,String> getAllProperties() throws IOException {
        Properties pps = new Properties();
        InputStream in =ConfigUtil.class.getClassLoader().getResourceAsStream(ConfigInfo.PROPERTIES_DEFAULT);
        pps.load(in);
        in.close();
        Enumeration<?> en = pps.propertyNames(); // 得到配置文件的名字
        Map<String,String> map = new HashMap<String,String>();
        while (en.hasMoreElements()) {
            String strKey =  en.nextElement().toString();
            map.put(strKey,pps.getProperty(strKey));
        }
        return map;
    }
    public static String getProperty(String filePath,String key) throws IOException {
        Properties pps = new Properties();
        InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);
        pps.load(in);
        in.close();
        return pps.getProperty(key);
    }
    public static Object setProperty(String filePath,String propertyName,String propertyValue) throws URISyntaxException, IOException {
        Properties p=new Properties();
        InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);
        p.load(in);//
        in.close();
        Object o = p.setProperty(propertyName,propertyValue);//設置屬性值,如屬性不存在新建
        OutputStream out=new FileOutputStream(new File(ConfigUtil.class.getClassLoader().getResource(ConfigInfo.PROPERTIES_DEFAULT).toURI()));//輸出流
        p.store(out,"modify");//設置屬性頭,如不想設置,請把後面一個用""替換掉
        out.flush();//清空緩存,寫入磁盤
        out.close();//關閉輸出流
        ConfigInfo.initOrRefresh();//刷新緩存
        return o;
    }
    public static void setProperties(String filePath,Set<Entry<String, Object>> data) throws IOException, URISyntaxException{
        Properties p=new Properties();
        InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);
        p.load(in);//
        in.close();
        for ( Entry<String,Object> entry : data) { //先遍歷整個 people 對象  
            p.setProperty( entry.getKey(),entry.getValue().toString());//設置屬性值,如屬性不存在新建
        }  
        OutputStream out=new FileOutputStream(new File(ConfigUtil.class.getClassLoader().getResource(ConfigInfo.PROPERTIES_DEFAULT).toURI()));//輸出流
        p.store(out,"modify");//設置屬性頭,如不想設置,請把後面一個用""替換掉
        out.flush();//清空緩存,寫入磁盤
        out.close();//關閉輸出流
        ConfigInfo.initOrRefresh();//刷新緩存
    }
}

5.前端頁面(使用的是Freemarker模板)

<form id="formParams" action="post">
                <table class="table table-bordered table-striped">
                    <thead><tr><th>參數名</th><th>參數值</th><th>說明</th></tr></thead>
                    <tbody>
                        <tr><td colspan="3" align="center"><span style="font-weight: bolder;">郵件服務</span></td></tr>
                        <tr>
                            <td>郵件服務器主機(SMTP)</td>
                            <td><input type="text" name="mail_host" value="${mail_host!'smtp.163.com'}" style="width:100%;"/></td>
                            <td>郵件服務器主機host,目前只支持SMTP協議(可以是163或者qq)</td>
                        </tr>
                        <tr>
                            <td>郵件服務器端口</td>
                            <td><input type="number" name="mail_port" value="${mail_port!'25'}" style="width:100%;"/></td>
                            <td>郵件服務器端口</td>
                        </tr>
                        <tr>
                            <td>郵件服務登錄賬號</td>
                            <td><input type="email" name="mail_username" value="${mail_username!'[email protected]'}" style="width:100%;"/></td>
                            <td>登錄郵件服務器的賬號,例如[email protected]</td>
                        </tr>
                        <tr>
                            <td>郵件服務登錄密碼</td>
                            <td><input type="password" name="mail_password" value="${mail_password!'234'}" style="width:100%;"/></td>
                            <td>登錄郵件服務器的密碼,該密碼通常是通過短信動態授權第三方登錄的密碼</td>
                        </tr>
                        <tr>
                            <td>連接服務器超時(毫秒)</td>
                            <td><input type="number" name="mail_smtp_timeout" value="${mail_smtp_timeout!'25000'}" style="width:100%;"/></td>
                            <td>使用賬號密碼登錄郵件服務器連接超時(毫秒)</td>
                        </tr>
                        <tr>
                            <td>郵件的發送賬號</td>
                            <td><input type="email" name="mail_from" value="${mail_from!'[email protected]'}" style="width:100%;"/></td>
                            <td>郵件的發送賬號,用於系統發送郵件的賬號,例如[email protected]</td>
                        </tr>
                        <tr>
                            <td>發送測試郵件賬號,看配置是否正確</td>
                            <td><input type="email" id="mailTo" placeholder="[email protected]" style="width:100%;"/></td>
                            <td><button type="button" class="btn btn-primary" onclick="sendTestMail()">發送測試郵件</button></td>
                        </tr>
                        <tr><td colspan="3" align="center">
                            <button type="button" class="btn btn-primary" onclick="saveParams()">保存</button>
                            <button type="button" class="btn btn-primary" onclick="$('#formParams')[0].reset()">重置</button>
                        </td></tr>
                    </tbody>
                </table>
                </form>
<script>
var regMail = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/;
/* 功能 */
function saveParams(){
    if(confirm("更改參數設置很有可能會導致系統功能異常(如果出現問題,請聯繫管理員),確定要保存更改嗎?"))
    {
        var from = $('#formParams input[name=mail_from]').val();
        var username = $('#formParams input[name=mail_username]').val();
        if(!regMail.test(from) || !regMail.test(username)){
            alert('郵箱格式不正確,請輸入有效的郵件賬號!');
            return ;
        }
        var data = $('#formParams').serializeArray();
        var obj=new Object(); 
        //將array轉換成JSONObject
        $.each(data,function(index,param){  
            if(!(param.name in obj)){  
                obj[param.name]=param.value;  
            }  
        });  
        $.ajax({
            type: "POST",
            url: "params_modify.do",
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(obj),
            dataType: "json",
            success: function (message) {
                alert(message.info);
            },
            error: function (message) {
                alert(message);
            }
        });
    }
}
function sendTestMail(){
    var to = $('#mailTo').val();
    var from = $('#formParams input[name=mail_from]').val();
    var username = $('#formParams input[name=mail_username]').val();
    var password = $('#formParams input[name=mail_password]').val();
    var host  = $('#formParams input[name=mail_host]').val();
    var port  = $('#formParams input[name=mail_port]').val();
    if(!regMail.test(to) || !regMail.test(from) || !regMail.test(username)){
        alert('郵箱格式不正確,請輸入有效的郵件賬號!');
        return ;
    }
    var p = {mail_host:host,mail_port:port,mail_username:username,mail_password:password,mail_from:from,mail_to:to};
    $.post("params_sendTestMail.do",p,function(data){
        data = eval('('+data+')');
        alert(data.info);
    });
}
</script>

採用的是ajax提交json的方式,注意dataType要設置爲json,即提交的json內容類似於 {mail_username:xxx,mail_password:xxx……}

6. 控制器Controller的寫法

@RequestMapping(value = "/params_modify.do", produces="text/html;charset=utf-8",method=RequestMethod.POST)
    @ResponseBody
    public String params_modify(@RequestBody String data){
        try {
            JSONObject jo = JSONObject.parseObject(data);
            ConfigUtil.setProperties(jo.entrySet());
            return JSON.toJSONString(new Result(true,"參數設置成功!"));
        } catch (IOException e) {
            e.printStackTrace();
            return JSON.toJSONString(new Result(false,"出現IO異常:可能配置文件找不到"));
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return JSON.toJSONString(new Result(false,"出現URISyntax異常:可能配置文件不對"));
        }
    }
    @RequestMapping(value = "/params_sendTestMail.do", produces="text/html;charset=utf-8",method=RequestMethod.POST)
    @ResponseBody
    public String params_sendTestMail(String mail_host,int mail_port,String mail_username,String mail_password,
                    String mail_from,String mail_to){
        try{
            MailUtil.sendMailForTest(mail_host,mail_port,mail_username,mail_password,mail_from,mail_to);
            return JSON.toJSONString(new Result(true,"測試郵件發送成功,請注意查收!"));
        }catch (MailAuthenticationException e) {
            e.printStackTrace();
            return JSON.toJSONString(new Result(false,"郵件認證異常:authentication failure(認證失敗)"));
        }catch(MailSendException e){
            e.printStackTrace();
            return JSON.toJSONString(new Result(false,"郵件發送異常:failure when sending the message(發送消息失敗)"));
        }catch(MailParseException  e){
            e.printStackTrace();
            return JSON.toJSONString(new Result(false,"郵件消息解析異常:failure when parsing the message(消息解析失敗)"));
        }

    }

控制器通過@RequestBody 來接收前端提交的json格式的字符串數據。
通過修改properties屬性文件,更新完成後讓ConfigInfo調用它的initOrRefresh()方法重新讀取一次配置文件,這樣就不必重啓應用了,注意需要將tomcat的server.xml中的reloadable設置成false,否則更改類路徑下的properties文件後tomcat會重新啓動該應用
<Context docBase="HNDYX" path="/demo" reloadable="false" source="org.eclipse.jst.jee.server:HNDYX"/></Host>

總結:上面用到的東西還是蠻多的,其中屬性文件的讀寫可能要花費一點時間去理解。Spring封裝的郵件API使用起來非常簡單。在實際應用中,系統參數配置修改還是常見的需求,一般這種鍵值對的配置用一個屬性文件保存即可,通過修改屬性文件達到修改參數配置的目的,對於不需要更改的只讀的屬性文件,例如jdbc.properties,那使用Spring容器來管理加載一次即可,這些數據庫的連接等信息並不需要動態更改,如果真的需要動態切換數據庫,那麼可以參考上面提供的一種思路。
另外當然可以採用數據庫存儲的方式來實現,單獨建立一張鍵值對的表,配置參數的修改則轉換爲了對普通表的增刪改。

最後在本地測試,需要你的計算機安裝SMTP服務,控制面板-》添加功能-》添加 SMTP服務,並把它開啓。

7.讀寫WEB-INF目錄下的屬性文件

修改class路徑下的屬性文件會導致tomcat重啓服務,這並不是我們所期望的。所以將屬性文件放在其他目錄下例如直接放在WEB-INF目錄下(與web.xml一樣),這樣在修改屬性文件後並不會導致Tomcat重啓。所以下面需要修改下代碼 ConfigUtil.java:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

//WEB-INF 目錄下的屬性文件 的讀寫  
public class ConfigUtil {

    public static String getProperty(String key) throws IOException {
        return getProperty(ConfigInfo.PROPERTIES_DEFAULT,key);
    }
    public static String getProperty(String filePath,String key) throws IOException {
        Properties pps = new Properties();
        //InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);
        String path = getPathForWebinf();
        InputStream in = new FileInputStream(path+filePath);
        pps.load(in);
        in.close();
        return pps.getProperty(key);
    }
    public static Object setProperty(String propertyName,String propertyValue) throws URISyntaxException, IOException {
        return setProperty(ConfigInfo.PROPERTIES_DEFAULT,propertyName,propertyValue);
    }
    public static void setProperties(Set<Entry<String, Object>> data) throws IOException, URISyntaxException{
        setProperties(ConfigInfo.PROPERTIES_DEFAULT,data);
    }
    // 讀取Properties的全部信息
    public static Map<String,String> getAllProperties() throws IOException {
        Properties pps = new Properties();
        String path = getPathForWebinf();
        InputStream in = new FileInputStream(path+ConfigInfo.PROPERTIES_DEFAULT);
        //InputStream in =ConfigUtil.class.getClassLoader().getResourceAsStream(ConfigInfo.PROPERTIES_DEFAULT);
        pps.load(in);
        in.close();
        Enumeration<?> en = pps.propertyNames();
        Map<String,String> map = new HashMap<String,String>();
        while (en.hasMoreElements()) {
            String strKey =  en.nextElement().toString();
            map.put(strKey,pps.getProperty(strKey));
        }
        return map;
    }
    public static Object setProperty(String filePath,String propertyName,String propertyValue) throws URISyntaxException, IOException {
        Properties p=new Properties();
        String path = getPathForWebinf();
        //InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);
        InputStream in = new FileInputStream(path+filePath);
        p.load(in);//
        in.close();
        Object o = p.setProperty(propertyName,propertyValue);//設置屬性值,如屬性不存在新建
        OutputStream out=new FileOutputStream(path+filePath);
        p.store(out,"modify");//設置屬性頭,如不想設置,請把後面一個用""替換掉
        out.flush();//清空緩存,寫入磁盤
        out.close();//關閉輸出流
        ConfigInfo.initOrRefresh();//刷新緩存
        return o;
    }
    public static void setProperties(String filePath,Set<Entry<String, Object>> data) throws IOException, URISyntaxException{
        Properties p=new Properties();
        String path = getPathForWebinf();
        InputStream in = new FileInputStream(path+filePath);
        //InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);
        p.load(in);//
        in.close();

        for ( Entry<String,Object> entry : data) { //先遍歷整個 people 對象  
            p.setProperty( entry.getKey(),entry.getValue().toString());//設置屬性值,如屬性不存在新建
        }  
        OutputStream out=new FileOutputStream(path+filePath);
        //new File(ConfigUtil.class.getClassLoader().getResource(ConfigInfo.PROPERTIES_DEFAULT).toURI()));//輸出流
        p.store(out,"modify");//設置屬性頭,如不想設置,請把後面一個用""替換掉
        out.flush();//清空緩存,寫入磁盤
        out.close();//關閉輸出流
        ConfigInfo.initOrRefresh();//刷新緩存
    }
    //獲取WEB-INF路徑
    public static String getPathForWebinf(){
        String path = ConfigUtil.class.getResource("/").getPath();//得到工程名WEB-INF/classes/路徑
        path=path.substring(1, path.indexOf("classes"));//從路徑字符串中取出工程路徑
        return path;
    }
}

另外需要改ConfigInfo.java文件中的一個地方:
class路徑的InputStream 改成 WEB-INF路徑的InputStream

InputStream in = new FileInputStream(ConfigUtil.getPathForWebinf()+PROPERTIES_DEFAULT);
            //InputStream in = ConfigInfo.class.getClassLoader().getResourceAsStream(PROPERTIES_DEFAULT);

8.Controller中讀取屬性文件

例如:

@Controller
public class SimpleController {
    @Autowired
    private ResourceLoader resourceLoader;

    @RequestMapping("/WEB-INF-file")
    @ResponseBody
    public String testProperties() throws IOException {
        String content = IOUtils.toString(resourceLoader.getResource("/WEB-INF/target_file.txt").getInputStream());
        return "the content of resources:" + content;
    }
}

請參考 Read file under WEB-INF directory example

拓展閱讀:270.道德經 第六十章3
非其神不傷人也,聖人亦弗傷也。夫兩不相傷,故德交歸焉。
譯:不是靈驗了就不傷人,是因爲有道的領導者也不傷人。這兩個都不傷害老百姓,德行都讓老百姓收益了。
即便是有鬼神之力,最終也是被人的意志影響,或幫助,或傷害,取決於人自身的行爲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章