public abstract class BaseController {

    public static String THEME = "themes/default";

    protected MapCache cache = MapCache.single();

    public String render(String viewName) {
        return THEME + "/" + viewName;
    }

    public BaseController title(Request request, String title) {
        request.attribute("title", title);
        return this;
    }

    public BaseController keywords(Request request, String keywords) {
        request.attribute("keywords", keywords);
        return this;
    }

    public Users user() {
        return TaleUtils.getLoginUser();
    }

    public Integer getUid(){
        return this.user().getUid();
    }

    public String render_404() {
        return "/comm/error_404";
    }

} public final class AdminCommons {

    public static String attachURL(){
        return Commons.site_option(Types.ATTACH_URL, Commons.site_url());
    }

    public static int maxFileSize(){
        return TaleConst.MAX_FILE_SIZE / 1024;
    }

    public static String cdnURL(){
        return Commons.site_option(Types.CDN_URL, "/static/admin");
    }

    public static String siteTheme() {
        return Commons.site_theme();
    }

} public final class Commons {

    private static final String TEMPLATES = "/templates/";

    public static void setSiteService(SiteService ss) {
        Theme.setSiteService(ss);
    }

    /**
     * 判断分页中是否有数据
     *
     * @param paginator
     * @return
     */
    public static boolean is_empty(Page<?> paginator) {
        return null == paginator || BladeKit.isEmpty(paginator.getRows());
    }

    /**
     * 判断字符串不为空
     *
     * @param str
     * @return
     */
    public static boolean not_empty(String str) {
        return StringKit.isNotBlank(str);
    }

    /**
     * 返回网站首页链接，如：http://tale.biezhi.me
     *
     * @return
     */
    public static String site_url() {
        return site_url("");
    }

    /**
     * 返回当前主题名称
     *
     * @return
     */
    public static String site_theme() {
        return site_option("site_theme", "default");
    }

    /**
     * 返回网站链接下的全址
     *
     * @param sub 后面追加的地址
     * @return
     */
    public static String site_url(String sub) {
        return site_option("site_url") + sub;
    }

    /**
     * 网站标题
     *
     * @return
     */
    public static String site_title() {
        return site_option("site_title");
    }

    /**
     * 网站子标题
     *
     * @return
     */
    public static String site_subtitle() {
        return site_option("site_subtitle");
    }

    /**
     * 是否允许使用云公共静态资源
     *
     * @return
     */
    public static String allow_cloud_CDN() {
        return site_option("allow_cloud_CDN");
    }

    /**
     * 网站配置项
     *
     * @param key
     * @return
     */
    public static String site_option(String key) {
        return site_option(key, "");
    }

    /**
     * 网站配置项
     *
     * @param key
     * @param defalutValue 默认值
     * @return
     */
    public static String site_option(String key, String defalutValue) {
        if (StringKit.isBlank(key)) {
            return "";
        }
        return TaleConst.OPTIONS.get(key, defalutValue);
    }

    /**
     * 返回站点设置的描述信息
     *
     * @return
     */
    public static String site_description() {
        return site_option("site_description");
    }

    /**
     * 截取字符串
     *
     * @param str
     * @param len
     * @return
     */
    public static String substr(String str, int len) {
        if (str.length() > len) {
            return str.substring(0, len);
        }
        return str;
    }

    /**
     * 返回主题URL
     *
     * @return
     */
    public static String theme_url() {
        return Commons.site_url(TEMPLATES + BaseController.THEME);
    }

    /**
     * 返回主题下的文件路径
     *
     * @param sub
     * @return
     */
    public static String theme_url(String sub) {
        return Commons.site_url(TEMPLATES + BaseController.THEME + sub);
    }


    /**
     * 返回gravatar头像地址
     *
     * @param email
     * @return
     */
    public static String gravatar(String email) {
        String avatarUrl = "https://cn.gravatar.com/avatar";
        if (StringKit.isBlank(email)) {
            return avatarUrl;
        }
        String hash = EncryptKit.md5(email.trim().toLowerCase());
        return avatarUrl + "/" + hash;
    }

    /**
     * 格式化unix时间戳为日期
     *
     * @param unixTime
     * @return
     */
    public static String fmtdate(Integer unixTime) {
        return fmtdate(unixTime, "yyyy-MM-dd");
    }

    /**
     * 格式化日期
     *
     * @param date
     * @param fmt
     * @return
     */
    public static String fmtdate(Date date, String fmt) {
        return DateKit.toString(date, fmt);
    }

    /**
     * 格式化unix时间戳为日期
     *
     * @param unixTime
     * @param patten
     * @return
     */
    public static String fmtdate(Integer unixTime, String patten) {
        if (null != unixTime && StringKit.isNotBlank(patten)) {
            return DateKit.toString(unixTime, patten);
        }
        return "";
    }

    /**
     * 获取随机数
     *
     * @param max
     * @param str
     * @return
     */
    public static String random(int max, String str) {
        return UUID.random(1, max) + str;
    }

    /**
     * An :grinning:awesome :smiley:string &#128516;with a few :wink:emojis!
     * <p>
     * 这种格式的字符转换为emoji表情
     *
     * @param value
     * @return
     */
    public static String emoji(String value) {
        return EmojiParser.parseToUnicode(value);
    }

    private static final Pattern SRC_PATTERN = Pattern.compile("src\\s*=\\s*\'?\"?(.*?)(\'|\"|>|\\s+)");
    /**
     * 获取文章第一张图片
     *
     * @return
     */
    public static String show_thumb(String content) {
        content = TaleUtils.mdToHtml(content);
        if (content.contains("<img")) {
            String  img       = "";
            String  regEx_img = "<img.*src\\s*=\\s*(.*?)[^>]*?>";
            Pattern p_image   = Pattern.compile(regEx_img, Pattern.CASE_INSENSITIVE);
            Matcher m_image   = p_image.matcher(content);
            if (m_image.find()) {
                img = img + "," + m_image.group();
                // //匹配src
                Matcher m = SRC_PATTERN.matcher(img);
                if (m.find()) {
                    return m.group(1);
                }
            }
        }
        return "";
    }

} public class JetTag {

    public static void social(JetTagContext ctx, String name) throws IOException {
        String value = Commons.site_option("social_" + name);
        if (StringKit.isNotBlank(value)) {
            value = ctx.getBodyContent();
        }
        ctx.getWriter().print(value.toString());
    }

} public final class Theme {

    private static SiteService siteService;

    public static void setSiteService(SiteService ss) {
        siteService = ss;
    }

    /**
     * 获取header keywords
     *
     * @return
     */
    public static String meta_keywords() {
        InterpretContext ctx   = InterpretContext.current();
        Object           value = ctx.getValueStack().getValue("keywords");
        if (null != value) {
            return value.toString();
        }
        return Commons.site_option("site_keywords");
    }

    /**
     * 获取header description
     *
     * @return
     */
    public static String meta_description() {
        InterpretContext ctx   = InterpretContext.current();
        Object           value = ctx.getValueStack().getValue("description");
        if (null != value) {
            return value.toString();
        }
        return Commons.site_option("site_description");
    }

    /**
     * header title
     *
     * @return
     */
    public static String head_title() {
        InterpretContext ctx   = InterpretContext.current();
        Object           value = ctx.getValueStack().getValue("title");

        String p = "";
        if (null != value) {
            p = value.toString() + " - ";
        }
        return p + Commons.site_option("site_title", "Tale 博客");
    }

    /**
     * 返回文章链接地址
     *
     * @return
     */
    public static String permalink() {
        Contents contents = current_article();
        return null != contents ? permalink(contents) : "";
    }

    /**
     * 返回文章链接地址
     *
     * @param contents
     * @return
     */
    public static String permalink(Contents contents) {
        return permalink(contents.getCid(), contents.getSlug());
    }

    /**
     * 返回文章链接地址
     *
     * @param cid
     * @param slug
     * @return
     */
    public static String permalink(Integer cid, String slug) {
        return Commons.site_url("/article/" + (StringKit.isNotBlank(slug) ? slug : cid.toString()));
    }

    /**
     * 显示文章创建日期
     *
     * @param fmt
     * @return
     */
    public static String created(String fmt) {
        Contents contents = current_article();
        if (null != contents) {
            return Commons.fmtdate(contents.getCreated(), fmt);
        }
        return "";
    }

    /**
     * 获取文章最后修改时间
     *
     * @param fmt
     * @return
     */
    public static String modified(String fmt) {
        Contents contents = current_article();
        if (null != contents) {
            return Commons.fmtdate(contents.getModified(), fmt);
        }
        return "";
    }

    /**
     * 返回文章浏览数
     *
     * @return
     */
    public static Integer hits() {
        Contents contents = current_article();
        return null != contents ? contents.getHits() : 0;
    }

    /**
     * 显示分类
     *
     * @return
     */
    public static String show_categories() throws UnsupportedEncodingException {
        Contents contents = current_article();
        if (null != contents) {
            return show_categories(contents.getCategories());
        }
        return "";
    }

    /**
     * 当前文章的分类列表
     *
     * @return
     * @since b1.3.0
     */
    public static List<String> category_list() {
        Contents contents = current_article();
        if (null != contents && StringKit.isNotBlank(contents.getCategories())) {
            return Arrays.asList(contents.getCategories().split(","));
        }
        return Collections.emptyList();
    }

    /**
     * 当前文章的标签列表
     *
     * @return
     * @since b1.3.0
     */
    public static List<String> tag_list() {
        Contents contents = current_article();
        if (null != contents && StringKit.isNotBlank(contents.getTags())) {
            return Arrays.asList(contents.getTags().split(","));
        }
        return Collections.emptyList();
    }

    /**
     * 显示分类
     *
     * @param categories
     * @return
     */
    public static String show_categories(String categories) throws UnsupportedEncodingException {
        if (StringKit.isNotBlank(categories)) {
            String[]     arr  = categories.split(",");
            StringBuffer sbuf = new StringBuffer();
            for (String c : arr) {
                sbuf.append("<a href=\"/category/" + URLEncoder.encode(c, "UTF-8") + "\">" + c + "</a>");
            }
            return sbuf.toString();
        }
        return show_categories("默认分类");
    }

    /**
     * 显示标签
     *
     * @param split 每个标签之间的分隔符
     * @return
     */
    public static String show_tags(String split) throws UnsupportedEncodingException {
        Contents contents = current_article();
        if (StringKit.isNotBlank(contents.getTags())) {
            String[]     arr  = contents.getTags().split(",");
            StringBuffer sbuf = new StringBuffer();
            for (String c : arr) {
                sbuf.append(split).append("<a href=\"/tag/" + URLEncoder.encode(c, "UTF-8") + "\">" + c + "</a>");
            }
            return split.length() > 0 ? sbuf.substring(split.length() - 1) : sbuf.toString();
        }
        return "";
    }

    /**
     * 显示文章浏览量
     *
     * @return
     */
    public static String views() {
        Contents contents = current_article();
        return null != contents ? contents.getHits().toString() : "0";
    }

    /**
     * 显示标签
     *
     * @return
     */
    public static String show_tags() throws UnsupportedEncodingException {
        return show_tags("");
    }

    /**
     * 显示文章内容，格式化markdown后的
     *
     * @return
     */
    public static String show_content() {
        Contents contents = current_article();
        return null != contents ? article(contents.getContent()) : "";
    }

    /**
     * 获取文章摘要
     *
     * @param len
     * @return
     */
    public static String excerpt(int len) {
        return intro(len);
    }

    /**
     * 获取文章摘要
     *
     * @param len
     * @return
     */
    public static String intro(int len) {
        Contents contents = current_article();
        if (null != contents) {
            return intro(contents.getContent(), len);
        }
        return "";
    }

    /**
     * 截取文章摘要(返回HTML)
     *
     * @param value 文章内容
     * @return 转换 markdown 为 html
     */
    public static String intro(String value) {
        if (StringKit.isBlank(value)) {
            return null;
        }
        int pos = value.indexOf("<!--more-->");
        if (pos != -1) {
            String html = value.substring(0, pos);
            return TaleUtils.mdToHtml(html);
        } else {
            return TaleUtils.mdToHtml(value);
        }
    }

    /**
     * 截取文章摘要
     *
     * @param value 文章内容
     * @param len   要截取文字的个数
     * @return
     */
    public static String intro(String value, int len) {
        int pos = value.indexOf("<!--more-->");
        if (pos != -1) {
            String html = value.substring(0, pos);
            return TaleUtils.htmlToText(TaleUtils.mdToHtml(html));
        } else {
            String text = TaleUtils.htmlToText(TaleUtils.mdToHtml(value));
            if (text.length() > len) {
                return text.substring(0, len);
            }
            return text;
        }
    }

    /**
     * 显示文章内容，转换markdown为html
     *
     * @param value
     * @return
     */
    public static String article(String value) {
        if (StringKit.isNotBlank(value)) {
            value = value.replace("<!--more-->", "\r\n");
            return TaleUtils.mdToHtml(value);
        }
        return "";
    }

    /**
     * 显示文章缩略图，顺序为：文章第一张图 -> 随机获取
     *
     * @return
     */
    public static String show_thumb(Contents contents) {
        if (null == contents) {
            return "";
        }
        if (StringKit.isNotBlank(contents.getThumbImg())) {
            String newFileName       = TaleUtils.getFileName(contents.getThumbImg());
            String thumbnailImgUrl = (contents.getThumbImg()).replace(newFileName, "thumbnail_" + newFileName);
            return thumbnailImgUrl;
        }
        String content = article(contents.getContent());
        String img     = Commons.show_thumb(content);
        if (StringKit.isNotBlank(img)) {
            return img;
        }
        int cid  = contents.getCid();
        int size = cid % 20;
        size = size == 0 ? 1 : size;
        return "/templates/themes/default/static/img/rand/" + size + ".jpg";
    }

    /**
     * 获取当前文章的下一篇
     *
     * @return
     */
    public static Contents article_next() {
        Contents cur = current_article();
        return null != cur ? siteService.getNhContent(Types.NEXT, cur.getCreated()) : null;
    }

    /**
     * 获取当前文章的上一篇
     *
     * @return
     */
    public static Contents article_prev() {
        Contents cur = current_article();
        return null != cur ? siteService.getNhContent(Types.PREV, cur.getCreated()) : null;
    }

    /**
     * 当前文章的下一篇文章链接
     *
     * @return
     */
    public static String theNext() {
        Contents contents = article_next();
        if (null != contents) {
            return theNext(title(contents));
        }
        return "";
    }

    /**
     * 当前文章的下一篇文章链接
     *
     * @param title 文章标题
     * @return
     */
    public static String theNext(String title) {
        Contents contents = article_next();
        if (null != contents) {
            return "<a href=\"" + permalink(contents) + "\" title=\"" + title(contents) + "\">" + title + "</a>";
        }
        return "";
    }

    /**
     * 当前文章的下一篇文章链接
     *
     * @return
     */
    public static String thePrev() {
        Contents contents = article_prev();
        if (null != contents) {
            return thePrev(title(contents));
        }
        return "";
    }

    /**
     * 当前文章的下一篇文章链接
     *
     * @param title 文章标题
     * @return
     */
    public static String thePrev(String title) {
        Contents contents = article_prev();
        if (null != contents) {
            return "<a href=\"" + permalink(contents) + "\" title=\"" + title(contents) + "\">" + title + "</a>";
        }
        return "";
    }

    /**
     * 最新文章
     *
     * @param limit
     * @return
     */
    public static List<Contents> recent_articles(int limit) {
        if (null == siteService) {
            return new ArrayList<>(0);
        }
        return siteService.getContens(Types.RECENT_ARTICLE, limit);
    }

    /**
     * 随机获取文章
     *
     * @param limit
     * @return
     */
    public static List<Contents> rand_articles(int limit) {
        if (null == siteService) {
            return new ArrayList<>(0);
        }
        return siteService.getContens(Types.RANDOM_ARTICLE, limit);
    }

    /**
     * 最新评论
     *
     * @param limit
     * @return
     */
    public static List<Comments> recent_comments(int limit) {
        if (null == siteService) {
            return new ArrayList<>(0);
        }
        return siteService.recentComments(limit);
    }

    /**
     * 获取分类列表
     *
     * @return
     */
    public static List<Metas> categories(int limit) {
        if (null == siteService) {
            return new ArrayList<>(0);
        }
        return siteService.getMetas(Types.RECENT_META, Types.CATEGORY, limit);
    }

    /**
     * 随机获取limit个分类
     *
     * @param limit
     * @return
     */
    public static List<Metas> rand_categories(int limit) {
        if (null == siteService) {
            return new ArrayList<>(0);
        }
        return siteService.getMetas(Types.RANDOM_META, Types.CATEGORY, limit);
    }

    /**
     * 获取所有分类
     *
     * @return
     */
    public static List<Metas> categories() {
        return categories(TaleConst.MAX_POSTS);
    }

    /**
     * 获取标签列表
     *
     * @return
     */
    public static List<Metas> tags(int limit) {
        if (null == siteService) {
            return new ArrayList<>(0);
        }
        return siteService.getMetas(Types.RECENT_META, Types.TAG, limit);
    }

    /**
     * 随机获取limit个标签
     *
     * @param limit
     * @return
     */
    public static List<Metas> rand_tags(int limit) {
        if (null == siteService) {
            return new ArrayList<>(0);
        }
        return siteService.getMetas(Types.RANDOM_META, Types.TAG, limit);
    }

    /**
     * 获取所有标签
     *
     * @return
     */
    public static List<Metas> tags() {
        return tags(TaleConst.MAX_POSTS);
    }

    /**
     * 获取评论at信息
     *
     * @param coid
     * @return
     */
    public static String comment_at(Integer coid) {
        if (null == siteService) {
            return "";
        }
        Comments comments = siteService.getComment(coid);
        if (null != comments) {
            return "<a href=\"#comment-" + coid + "\">@" + comments.getAuthor() + "</a>";
        }
        return "";
    }

    private static final String[] ICONS = {"bg-ico-book", "bg-ico-game", "bg-ico-note", "bg-ico-chat", "bg-ico-code", "bg-ico-image", "bg-ico-web", "bg-ico-link", "bg-ico-design", "bg-ico-lock"};

    /**
     * 显示文章图标
     *
     * @return
     */
    public static String show_icon() {
        Contents contents = current_article();
        if (null != contents) {
            return show_icon(contents.getCid());
        }
        return show_icon(1);
    }

    /**
     * 显示文章图标
     *
     * @param cid
     * @return
     */
    public static String show_icon(int cid) {
        return ICONS[cid % ICONS.length];
    }

    /**
     * 显示文章标题
     *
     * @return
     */
    public static String title() {
        return title(current_article());
    }

    /**
     * 返回文章标题
     *
     * @param contents
     * @return
     */
    public static String title(Contents contents) {
        return null != contents ? contents.getTitle() : Commons.site_title();
    }

    /**
     * 返回社交账号链接
     *
     * @param type
     * @return
     */
    public static String social_link(String type) {
        String id = Commons.site_option("social_" + type);
        switch (type) {
            case "github":
                return "https://github.com/" + id;
            case "weibo":
                return "http://weibo.com/" + id;
            case "twitter":
                return "https://twitter.com/" + id;
            case "zhihu":
                return "https://www.zhihu.com/people/" + id;
            default:
                return null;
        }
    }

    /**
     * 获取当前文章/页面的评论
     *
     * @param limit
     * @return
     */
    public static Page<Comment> comments(int limit) {
        Contents contents = current_article();
        if (null == contents) {
            return new Page<>();
        }
        InterpretContext ctx   = InterpretContext.current();
        Object           value = ctx.getValueStack().getValue("cp");
        int              page  = 1;
        if (null != value) {
            page = (int) value;
        }
        Page<Comment> comments = siteService.getComments(contents.getCid(), page, limit);
        return comments;
    }

    /**
     * 获取当前文章/页面的评论数量
     *
     * @return 当前页面的评论数量
     */
    public static long commentsCount() {
        Contents contents = current_article();
        if (null == contents) {
            return 0;
        }
        return siteService.getCommentCount(contents.getCid());
    }

    /**
     * 分页
     *
     * @param limit
     * @return
     */
    public static Page<Contents> articles(int limit) {
        Request request = WebContext.request();
        Integer page    = request.attribute("page_num");
        page = null == page ? request.queryInt("page", 1) : page;
        page = page < 0 || page > TaleConst.MAX_PAGE ? 1 : page;

        Page<Contents> articles = select().from(Contents.class)
                .where(Contents::getType, Types.ARTICLE)
                .and("status", Types.PUBLISH)
                .order(Contents::getCreated, OrderBy.DESC)
                .page(page, limit);

        request.attribute("articles", articles);
        if (page > 1) {
            WebContext.request().attribute("title", "第" + page + "页");
        }
        request.attribute("is_home", true);
        request.attribute("page_prefix", "/page");
        return articles;
    }

    /**
     * 获取当前上下文的文章对象
     *
     * @return
     */
    private static Contents current_article() {
        InterpretContext ctx   = InterpretContext.current();
        Object           value = ctx.getValueStack().getValue("article");
        if (null != value) {
            return (Contents) value;
        }
        return null;
    }

    /**
     * 显示评论
     *
     * @param noComment 评论为0的时候显示的文本
     * @param value     评论组装文本
     * @return
     */
    public static String comments_num(String noComment, String value) {
        Contents contents = current_article();
        if (null == contents) {
            return noComment;
        }
        return contents.getCommentsNum() > 0 ? String.format(value, contents.getCommentsNum()) : noComment;
    }

    /**
     * 返回主题设置选项
     *
     * @param key
     * @return
     */
    public static String theme_option(String key, String defaultValue) {
        String option = theme_option(key);
        if (StringKit.isBlank(option)) {
            return defaultValue;
        }
        return option;
    }

    /**
     * 返回主题设置选项
     *
     * @param key
     * @return
     */
    public static String theme_option(String key) {
        String theme = Commons.site_theme();
        return TaleConst.OPTIONS.get("theme_" + theme + "_options")
                .filter(StringKit::isNotBlank)
                .map((String json) -> {
                    Ason<?,?> ason = JsonKit.toAson(json);
                    if (!ason.containsKey(key)) {
                        return "";
                    }
                    return ason.getString(key);
                })
                .orElse("");
    }

    /**
     * 返回是否是某个页面
     *
     * @param pageName
     * @return
     */
    public static boolean is_slug(String pageName) {
        Contents contents = current_article();
        if (null != contents && Types.PAGE.equals(contents.getType()) && contents.getSlug().equals(pageName)) {
            return true;
        }
        if (TaleConst.SLUG_HOME.equals(pageName)) {
            Boolean isHome = WebContext.request().attribute("is_home");
            if (null != isHome && isHome) {
                return true;
            }
        }
        if (TaleConst.SLUG_ARCHIVES.equals(pageName)) {
            Boolean isArchives = WebContext.request().attribute("is_archive");
            if (null != isArchives && isArchives) {
                return true;
            }
        }
        if (TaleConst.SLUG_CATEGRORIES.equals(pageName)) {
            Boolean isCategory = WebContext.request().attribute("is_category");
            if (null != isCategory && isCategory) {
                return true;
            }
        }
        if (TaleConst.SLUG_TAGS.equals(pageName)) {
            Boolean isTag = WebContext.request().attribute("is_tag");
            if (null != isTag && isTag) {
                return true;
            }
        }
        return false;
    }
} @Data
public class RememberMe {

    private Integer      uid;
    private Integer      expires;
    private String       token;
    private List<String> recentIp;

} public interface Types {

    String TAG = "tag";
    String CATEGORY = "category";
    String ARTICLE = "post";
    String PUBLISH = "publish";
    String PAGE = "page";
    String DRAFT = "draft";
    String IMAGE = "image";
    String FILE = "file";
    String COMMENTS_FREQUENCY = "comments:frequency";

    String RECENT_ARTICLE = "recent_article";
    String RANDOM_ARTICLE = "random_article";

    String RECENT_META = "recent_meta";
    String RANDOM_META = "random_meta";

    String SYS_STATISTICS = "sys:statistics";

    String NEXT = "next";
    String PREV = "prev";

    /**
     * 附件存放的URL，默认为网站地址，如集成第三方则为第三方CDN域名
     */
    String ATTACH_URL = "attach_url";
    String CDN_URL = "cdn_url";

    /**
     * 网站要过滤，禁止访问的ip列表
     */
    String BLOCK_IPS = "site_block_ips";

} @Bean
public class OptionsService {

    /**
     * 保存配置
     *
     * @param key   配置key
     * @param value 配置值
     */
    public void saveOption(String key, String value) {
        if (StringKit.isNotBlank(key) && StringKit.isNotBlank(value)) {
            Options options = new Options();
            options.setName(key);

            long count = select().from(Options.class).where(Options::getName, key).count();

            if (count == 0) {
                options = new Options();
                options.setName(key);
                options.setValue(value);
                options.save();
            } else {
                options = new Options();
                options.setValue(value);
                options.updateById(key);
            }
        }
    }

    /**
     * 获取系统配置
     */
    public Map<String, String> getOptions() {
        Map<String, String> options = new HashMap<>();
        AnimaQuery<Options> animaQuery = select().from(Options.class);
        List<Options> optionsList = animaQuery.all();
        if (null != optionsList) {
            optionsList.forEach(option -> options.put(option.getName(), option.getValue()));
        }
        return options;
    }

    public String getOption(String key) {
        Options options = select().from(Options.class).byId(key);
        if (null != options) {
            return options.getValue();
        }
        return null;
    }

    /**
     * 根据key删除配置项
     *
     * @param key 配置key
     */
    public void deleteOption(String key) {
        if (StringKit.isNotBlank(key)) {
            delete().from(Options.class).where(Options::getName).like(key + "%").execute();
        }
    }
} @Bean
public class SiteService {

    @Inject
    private CommentsService commentsService;

    public MapCache mapCache = new MapCache();

    /**
     * 初始化站点
     *
     * @param users 用户
     */
    public void initSite(Users users) {
        String pwd = EncryptKit.md5(users.getUsername() + users.getPassword());
        users.setPassword(pwd);
        users.setScreenName(users.getUsername());
        users.setCreated(DateKit.nowUnix());
        Integer uid = users.save().asInt();

        try {
            String cp   = SiteService.class.getClassLoader().getResource("").getPath();
            File   lock = new File(cp + "install.lock");
            lock.createNewFile();
            TaleConst.INSTALLED = Boolean.TRUE;
            new Logs("初始化站点", null, "", uid).save();
        } catch (Exception e) {
            throw new ValidatorException("初始化站点失败");
        }
    }

    /**
     * 最新收到的评论
     *
     * @param limit 评论数
     */
    public List<Comments> recentComments(int limit) {
        if (limit < 0 || limit > 10) {
            limit = 10;
        }

        Page<Comments> commentsPage = select().from(Comments.class)
                                              .where(Comments::getStatus, COMMENT_APPROVED)
                                              .order(Comments::getCreated, OrderBy.DESC)
                                              .page(1, limit);
        return commentsPage.getRows();
    }

    /**
     * 根据类型获取文章列表
     *
     * @param type 最新,随机
     * @param limit 获取条数
     */
    public List<Contents> getContens(String type, int limit) {

        if (limit < 0 || limit > 20) {
            limit = 10;
        }

        // 最新文章
        if (Types.RECENT_ARTICLE.equals(type)) {
            Page<Contents> contentsPage = select().from(Contents.class)
                                                  .where(Contents::getStatus, Types.PUBLISH)
                                                  .and(Contents::getType, Types.ARTICLE)
                                                  .order(Contents::getCreated, OrderBy.DESC)
                                                  .page(1, limit);

            return contentsPage.getRows();
        }

        // 随机文章
        if (Types.RANDOM_ARTICLE.equals(type)) {
            List<Integer> cids = select().bySQL(Integer.class,
                                                "select cid from t_contents where type = ? and status = ? order by random() * cid limit ?",
                                                Types.ARTICLE, Types.PUBLISH, limit).all();
            if (BladeKit.isNotEmpty(cids)) {
                return select().from(Contents.class).in(Contents::getCid, cids).all();
            }
        }
        return new ArrayList<>();
    }

    /**
     * 获取后台统计数据
     */
    public Statistics getStatistics() {

        Statistics statistics = mapCache.get(Types.SYS_STATISTICS);
        if (null != statistics) {
            return statistics;
        }

        statistics = new Statistics();

        long articles = select().from(Contents.class).where(Contents::getType, Types.ARTICLE)
                                .and(Contents::getStatus, Types.PUBLISH).count();
        long pages = select().from(Contents.class).where(Contents::getType, Types.PAGE)
                             .and(Contents::getStatus, Types.PUBLISH).count();
        long comments   = select().from(Comments.class).count();
        long attachs    = select().from(Attach.class).count();
        long tags       = select().from(Metas.class).where(Metas::getType, Types.TAG).count();
        long categories = select().from(Metas.class).where(Metas::getType, Types.CATEGORY).count();

        statistics.setArticles(articles);
        statistics.setPages(pages);
        statistics.setComments(comments);
        statistics.setAttachs(attachs);
        statistics.setTags(tags);
        statistics.setCategories(categories);

        mapCache.set(Types.SYS_STATISTICS, statistics);
        return statistics;
    }

    /**
     * 查询文章归档
     */
    public List<Archive> getArchives() {
        String sql =
            "select strftime('%Y年%m月', datetime(created, 'unixepoch') ) as date_str, count(*) as count  from t_contents "
                +
                "where type = 'post' and status = 'publish' group by date_str order by date_str desc";

        List<Archive> archives = select().bySQL(Archive.class, sql).all();
        if (null != archives) {
            return archives.stream()
                           .map(this::parseArchive)
                           .collect(Collectors.toList());
        }
        return new ArrayList<>(0);
    }

    private Archive parseArchive(Archive archive) {
        String dateStr = archive.getDateStr();
        Date   sd      = DateKit.toDate(dateStr + "01", "yyyy年MM月dd");
        archive.setDate(sd);
        int      start    = DateKit.toUnix(sd);
        Calendar calender = Calendar.getInstance();
        calender.setTime(sd);
        calender.add(Calendar.MONTH, 1);
        Date endSd = calender.getTime();
        int  end   = DateKit.toUnix(endSd) - 1;

        List<Contents> contents = select().from(Contents.class)
                                          .where(Contents::getType, Types.ARTICLE)
                                          .and(Contents::getStatus, Types.PUBLISH)
                                          .and(Contents::getCreated).gt(start)
                                          .and(Contents::getCreated).lt(end)
                                          .order(Contents::getCreated, OrderBy.DESC)
                                          .all();

        archive.setArticles(contents);
        return archive;
    }

    /**
     * 查询一条评论
     *
     * @param coid 评论主键
     */
    public Comments getComment(Integer coid) {
        if (null != coid) {
            return select().from(Comments.class).byId(coid);
        }
        return null;
    }

    /**
     * 系统备份
     */
    public BackResponse backup(String bkType, String bkPath, String fmt) throws Exception {
        BackResponse backResponse = new BackResponse();
        if ("attach".equals(bkType)) {
            if (StringKit.isBlank(bkPath)) {
                throw new ValidatorException("请输入备份文件存储路径");
            }
            if (!Files.isDirectory(Paths.get(bkPath))) {
                throw new ValidatorException("请输入一个存在的目录");
            }
            String bkAttachDir = CLASSPATH + "upload";
            String bkThemesDir = CLASSPATH + "templates/themes";

            String fname = DateKit.toString(new Date(), fmt) + "_" + StringKit.rand(5) + ".zip";

            String attachPath = bkPath + "/" + "attachs_" + fname;
            String themesPath = bkPath + "/" + "themes_" + fname;

            backResponse.setAttach_path(attachPath);
            backResponse.setTheme_path(themesPath);
        }
        // 备份数据库
        if ("db".equals(bkType)) {
            String filePath = "upload/" + DateKit.toString(new Date(), "yyyyMMddHHmmss") + "_"
                + StringKit.rand(8) + ".db";
            String cp = CLASSPATH + filePath;
            Files.createDirectory(Paths.get(cp));
            Files.copy(Paths.get(SqliteJdbc.DB_PATH), Paths.get(cp));
            backResponse.setSql_path("/" + filePath);
            // 10秒后删除备份文件
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    new File(cp).delete();
                }
            }, 10 * 1000);
        }
        return backResponse;
    }

    /**
     * 获取分类/标签列表
     */
    public List<Metas> getMetas(String searchType, String type, int limit) {

        if (StringKit.isBlank(searchType) || StringKit.isBlank(type)) {
            return new ArrayList<>(0);
        }

        if (limit < 1 || limit > TaleConst.MAX_POSTS) {
            limit = 10;
        }

        // 获取最新的项目
        if (Types.RECENT_META.equals(searchType)) {
            String sql =
                "select a.*, count(b.cid) as count from t_metas a left join `t_relationships` b on a.mid = b.mid "
                    +
                    "where a.type = ? group by a.mid order by count desc, a.mid desc limit ?";

            return select().bySQL(Metas.class, sql, type, limit).all();
        }

        // 随机获取项目
        if (Types.RANDOM_META.equals(searchType)) {
            List<Integer> mids = select().bySQL(Integer.class,
                                                "select mid from t_metas where type = ? order by random() * mid limit ?",
                                                type, limit).all();
            if (BladeKit.isNotEmpty(mids)) {
                String in = TaleUtils.listToInSql(mids);
                String sql =
                    "select a.*, count(b.cid) as count from t_metas a left join `t_relationships` b on a.mid = b.mid "
                        +
                        "where a.mid in " + in + "group by a.mid order by count desc, a.mid desc";

                return select().bySQL(Metas.class, sql).all();
            }
        }
        return new ArrayList<>(0);
    }

    /**
     * 获取相邻的文章
     *
     * @param type 上一篇:prev | 下一篇:next
     * @param created 当前文章创建时间
     */
    public Contents getNhContent(String type, Integer created) {
        Contents contents = null;
        if (Types.NEXT.equals(type)) {
            contents = select().bySQL(Contents.class,
                                      "SELECT * FROM t_contents WHERE type = ? AND status = ? AND created > ? ORDER BY created ASC LIMIT 1",
                                      Types.ARTICLE, Types.PUBLISH, created).one();
        }
        if (Types.PREV.equals(type)) {
            contents = select().bySQL(Contents.class,
                                      "SELECT * FROM t_contents WHERE type = ? AND status = ? AND created < ? ORDER BY created DESC LIMIT 1",
                                      Types.ARTICLE, Types.PUBLISH, created).one();
        }
        return contents;
    }

    /**
     * 获取文章的评论
     *
     * @param cid 文章id
     * @param page 页码
     * @param limit 每页条数
     */
    public Page<Comment> getComments(Integer cid, int page, int limit) {
        return commentsService.getComments(cid, page, limit);
    }

    /**
     * 获取文章的评论总数
     *
     * @param cid 文章id
     */
    public long getCommentCount(Integer cid) {
        return commentsService.getCommentCount(cid);
    }

    /**
     * 清楚缓存
     *
     * @param key 缓存key
     */
    public void cleanCache(String key) {
        if (StringKit.isNotBlank(key)) {
            if ("*".equals(key)) {
                mapCache.clean();
            } else {
                mapCache.del(key);
            }
        }
    }

} public static final String CLASSPATH = new File(AdminApiController.class.getResource("/").getPath()).getPath() + File.separatorChar; public static final String OPTION_SAFE_REMEMBER_ME    = "safe_remember_me";
import com.blade.Blade;
import com.blade.Environment;
import com.blade.ioc.Ioc;
import com.blade.ioc.annotation.Bean;
import com.blade.ioc.annotation.Inject;
import com.blade.kit.JsonKit;
import com.blade.kit.StringKit;
import com.blade.loader.BladeLoader;
import com.blade.mvc.view.template.JetbrickTemplateEngine;
import com.blade.validator.Validators;
import com.tale.controller.BaseController;
import com.tale.extension.AdminCommons;
import com.tale.extension.Commons;
import com.tale.extension.JetTag;
import com.tale.extension.Theme;
import com.tale.model.dto.RememberMe;
import com.tale.model.dto.Types;
import com.tale.service.OptionsService;
import com.tale.service.SiteService;
import io.github.biezhi.anima.Anima;
import jetbrick.template.JetGlobalContext;
import jetbrick.template.resolver.GlobalResolver;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.tale.bootstrap.TaleConst.CLASSPATH;
import static com.tale.bootstrap.TaleConst.OPTION_SAFE_REMEMBER_ME;
package com.tale.bootstrap;




/**
 * Tale初始化进程
 *
 * @author biezhi
 */
@Bean
public class Bootstrap implements BladeLoader {

    @Inject
    private OptionsService optionsService;

    @Inject
    private Environment environment;

    @Override
    public void preLoad(Blade blade) {
        Ioc ioc = blade.ioc();

        Validators.useChinese();

        boolean devMode = true;
        if (blade.environment().hasKey("app.dev")) {
            devMode = blade.environment().getBoolean("app.dev", true);
        }
        if (blade.environment().hasKey("app.devMode")) {
            devMode = blade.environment().getBoolean("app.devMode", true);
        }
        SqliteJdbc.importSql(devMode);
        Anima.open(SqliteJdbc.DB_SRC);
        Commons.setSiteService(ioc.getBean(SiteService.class));
    }

    @Override
    public void load(Blade blade) {
        JetbrickTemplateEngine templateEngine = new JetbrickTemplateEngine();

        List<String> macros = new ArrayList<>(8);
        macros.add(File.separatorChar + "comm" + File.separatorChar + "macros.html");
        // 扫描主题下面的所有自定义宏
        String themeDir = CLASSPATH + "templates" + File.separatorChar + "themes";
        File[] dir      = new File(themeDir).listFiles();
        if (null != dir) {
            for (File f : dir) {
                if (f.isDirectory() && Files.exists(Paths.get(f.getPath() + File.separatorChar + "macros.html"))) {
                    String macroName = File.separatorChar + "themes" + File.separatorChar + f.getName() + File.separatorChar + "macros.html";
                    macros.add(macroName);
                }
            }
        }

        StringBuffer sbuf = new StringBuffer();
        macros.forEach(s -> sbuf.append(',').append(s));
        templateEngine.addConfig("jetx.import.macros", sbuf.substring(1));

        GlobalResolver resolver = templateEngine.getGlobalResolver();
        resolver.registerFunctions(Commons.class);
        resolver.registerFunctions(Theme.class);
        resolver.registerFunctions(AdminCommons.class);
        resolver.registerTags(JetTag.class);