在开发Halo的时候,有很多设置需要保存在数据库里,比如站点标题,关键字等等。那么这时候问题就来了,怎么样设计表结构呢?设置选项是比较多的,不可能把每个设置选项都当成数据表字段吧?后来决定使用key-value键值对的形式来保存数据。

实现思路

数据表设计

CREATE TABLE halo_options
(
  option_name  VARCHAR(255) NOT NULL PRIMARY KEY,    -- key
  option_value LONGTEXT                            -- value
);

思路简述

整体思路是这样子的:在数据表中只设置两个字段,options_name用来保存设置选项名称,如site_title(网站标题)options_value用来保存设置选项对应的值,如Ryan0up'S Blog。那么怎么来查询设置选项呢?分两种,第一种是根据key(设置选项名称)来查询所对应的值。第二种是查询所有设置选项,这里就要用到Map来保存所有的设置选项以及对应的值了。

实现代码

Options(实体类):

@Data
@Entity
@Table(name = "halo_options")
public class Options implements Serializable {

    /**
     * 设置项名称
     */
    @Id
    private String optionName;

    /**
     * 设置项的值
     */
    @Lob
    private String optionValue;
}

OptionsServiceImpl(Service实现类):

@Service
public class OptionsServiceImpl implements OptionsService {

    @Autowired
    private OptionsRepository optionsRepository;

    private static final String OPTIONS_KEY = "'options_key'";

    private static final String OPTIONS_CACHE_NAME = "options_cache";

    /**
     * 批量保存设置
     *
     * @param options options
     */
    @CacheEvict(value = OPTIONS_CACHE_NAME,key = OPTIONS_KEY)
    @Override
    public void saveOptions(Map<String,String> options){
        if(null != options && !options.isEmpty()){
            options.forEach((k,v) -> saveOption(k,v));
        }
    }

    /**
     * 保存单个设置选项
     *
     * @param key key
     * @param value value
     */
    @CacheEvict(value = OPTIONS_CACHE_NAME,key = OPTIONS_KEY)
    @Override
    public void saveOption(String key,String value){
        Options options = null;
        if("".equals(value)){
            options = new Options();
            options.setOptionName(key);
            this.removeOption(options);
        }else {
            if (HaloUtil.isNotNull(key)) {
                //如果查询到有该设置选项则做更新操作,反之保存新的设置选项
                if (null == optionsRepository.findOptionsByOptionName(key)) {
                    options = new Options();
                    options.setOptionName(key);
                    options.setOptionValue(value);
                    optionsRepository.save(options);
                } else {
                    options = optionsRepository.findOptionsByOptionName(key);
                    options.setOptionValue(value);
                    optionsRepository.save(options);
                }
            }
        }
    }

    /**
     * 移除设置项
     *
     * @param options options
     */
    @CacheEvict(value = OPTIONS_CACHE_NAME,key = OPTIONS_KEY)
    @Override
    public void removeOption(Options options) {
        optionsRepository.delete(options);
    }

    /**
     * 获取设置选项
     *
     * @return map
     */
    @Cacheable(value = OPTIONS_CACHE_NAME,key = OPTIONS_KEY)
    @Override
    public Map<String, String> findAllOptions() {
        Map<String,String> options = new HashMap<String,String>();
        List<Options> optionsList = optionsRepository.findAll();
        if(null != optionsList){
            optionsList.forEach(option -> options.put(option.getOptionName(),option.getOptionValue()));
        }
        return options;
    }

    /**
     * 根据key查询单个设置选项
     *
     * @param key key
     * @return String
     */
    @Cacheable(value = OPTIONS_CACHE_NAME,key = "#key+'options'")
    @Override
    public String findOneOption(String key) {
        Options options = optionsRepository.findOptionsByOptionName(key);
        if(null!=options){
            return options.getOptionValue();
        }
        return null;
    }
}

OptionsController(控制器):

@Controller
@RequestMapping("/admin/option")
public class OptionController {

    @Autowired
    private OptionsService optionsService;

    /**
     * 请求跳转到option页面并完成渲染
     *
     * @return freemarker
     */
    @GetMapping
    public String options(Model model){
        model.addAttribute("options", optionsService.findAllOptions());
        return "admin/admin_option";
    }

    /**
     * 保存设置选项
     *
     * @param options options
     */
    @PostMapping(value = "/save")
    @ResponseBody
    public String saveOptions(@RequestParam Map<String,String> options){
        try {
            optionsService.saveOptions(options);
            HaloConst.OPTIONS.clear();
            HaloConst.OPTIONS = optionsService.findAllOptions();
            log.info("所保存的设置选项列表:"+options);
            return RespStatus.SUCCESS;
        }catch (Exception e){
            log.error("未知错误:",e.getMessage());
            return RespStatus.ERROR;
        }
    }
}

页面上获取设置选项的值:

由于是使用Map来保存的数据,所以在页面(freemarker模板,其他模板引擎类似)上直接使用${options.设置选项的名称}就可以了,如${options.blog_title}

总结

由于之前没有做过类似的功能,所以最开始做的时候简直毫无头绪,通过在v2exSegmentFault上提问才有了思路。非常感谢v2ex和SegmentFault上的大佬解答。