目录
一、整合微信公众号
1.1、公众号菜单管理
1.2、微信授权登录
1.3、消息推送
一、整合微信公众号
1.1、公众号菜单管理
数据库表:
CREATE TABLE `wechat_menu` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `parent_id` BIGINT(20) DEFAULT NULL COMMENT '上级id', `name` VARCHAR(50) DEFAULT NULL COMMENT '菜单名称', `type` VARCHAR(10) DEFAULT NULL COMMENT '类型', `url` VARCHAR(100) DEFAULT NULL COMMENT '网页 链接,用户点击菜单可打开链接', `meun_key` VARCHAR(20) DEFAULT NULL COMMENT '菜单KEY值,用于消息接口推送', `sort` TINYINT(3) DEFAULT NULL COMMENT '排序', `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `is_deleted` TINYINT(3) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='菜单';
后端接口:
@Service public class WechatMenuServiceImpl extends ServiceImpl<WechatMenuMapper, Menu> implements WechatMenuService { //获取全部菜单 @Override public List<MenuVo> findMenuInfo() { //查询所有菜单 List<Menu> menuList = baseMapper.selectList(null); //查询所有一级菜单,返回一级菜单集合 List<Menu> oneMenus = menuList .stream().filter(c -> c.getParentId() .longValue() == 0) .collect(Collectors.toList()); //遍历得到每一个一级菜单 ArrayList<MenuVo> arrayList = new ArrayList<>(); for (Menu menu : oneMenus){ MenuVo oneMenuVo = new MenuVo(); BeanUtils.copyProperties(menu,oneMenuVo); //获取每个菜单里面的二级id,通过二级菜单id与getParentId比较 List<Menu> twoMenuList = menuList .stream() .filter(c -> c.getParentId().longValue() == menu.getId().longValue()) .collect(Collectors.toList()); //把一级菜单里面所有二级菜单获得,封装到children里面 ArrayList<MenuVo> children = new ArrayList<>(); for (Menu twoMenu:twoMenuList){ MenuVo twoMenuVo = new MenuVo(); BeanUtils.copyProperties(twoMenu,twoMenuVo); children.add(twoMenuVo); } oneMenuVo.setChildren(children); //将封装的list集合添加到返回集合中 arrayList.add(oneMenuVo); } return arrayList; } }
controller层
@Api(tags = "微信公众号接口") @RestController @RequestMapping("admin/wechat/menu") public class WechatMenuController { @Autowired private WechatMenuService wechatMenuService; @ApiOperation("获取全部菜单") @GetMapping("/findMenuInfo") public Result findMenuInfo(){ List<MenuVo> metaVoList = wechatMenuService.findMenuInfo(); return Result.ok(metaVoList); } @ApiOperation("根据id查询") @GetMapping("/getById/{id}") public Result getById(@PathVariable("id") Long id){ return Result.ok(wechatMenuService.getById(id)); } @ApiOperation("添加") @PostMapping("/save") public Result save(@RequestBody Menu menu){ wechatMenuService.save(menu); return Result.ok(); } @ApiOperation("修改") @PutMapping("/update") public Result update(@RequestBody Menu menu){ wechatMenuService.updateById(menu); return Result.ok(); } @ApiOperation("删除") @DeleteMapping("/delete/{id}") public Result delete(@PathVariable Long id){ wechatMenuService.removeById(id); return Result.ok(); } }
前端代码:
创建src/api/wechat/menu.js
import request from '@/utils/request' const api_name = '/admin/wechat/menu' export default { findMenuInfo() { return request({ url: `${api_name}/findMenuInfo`, method: `get` }) }, save(menu) { return request({ url: `${api_name}/save`, method: `post`, data: menu }) }, getById(id) { return request({ url: `${api_name}/getById/${id}`, method: `get` }) }, updateById(menu) { return request({ url: `${api_name}/update`, method: `put`, data: menu }) }, removeById(id) { return request({ url: `${api_name}/delete/${id}`, method: 'delete' }) } }
创建views/wechat/menu/list.vue
<template> <div class="app-container"> <!-- 工具条 --> <div class="tools-div"> <el-button class="btn-add" size="mini" @click="add">添 加</el-button> </div> <el-table :data="list" style="width: 100%;margin-bottom: 20px;" row-key="id" border default-expand-all :tree-props="{children: 'children'}"> <el-table-column label="名称" prop="name" width="350"></el-table-column> <el-table-column label="类型" width="100"> <template slot-scope="scope"> {{ scope.row.type == 'view' ? '链接' : scope.row.type == 'click' ? '事件' : '' }} </template> </el-table-column> <el-table-column label="菜单URL" prop="url" ></el-table-column> <el-table-column label="菜单KEY" prop="meunKey" width="130"></el-table-column> <el-table-column label="排序号" prop="sort" width="70"></el-table-column> <el-table-column label="操作" width="170" align="center"> <template slot-scope="scope"> <el-button v-if="scope.row.parentId > 0" type="text" size="mini" @click="edit(scope.row.id)">修改</el-button> <el-button v-if="scope.row.parentId > 0" type="text" size="mini" @click="removeDataById(scope.row.id)">删除</el-button> </template> </el-table-column> </el-table> <el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" > <el-form ref="flashPromotionForm" label-width="150px" size="small" style="padding-right: 40px;"> <el-form-item label="选择一级菜单"> <el-select v-model="menu.parentId" placeholder="请选择"> <el-option v-for="item in list" :key="item.id" :label="item.name" :value="item.id"/> </el-select> </el-form-item> <el-form-item label="菜单名称"> <el-input v-model="menu.name"/> </el-form-item> <el-form-item label="菜单类型"> <el-radio-group v-model="menu.type"> <el-radio label="view">链接</el-radio> <el-radio label="click">事件</el-radio> </el-radio-group> </el-form-item> <el-form-item v-if="menu.type == 'view'" label="链接"> <el-input v-model="menu.url"/> </el-form-item> <el-form-item v-if="menu.type == 'click'" label="菜单KEY"> <el-input v-model="menu.meunKey"/> </el-form-item> <el-form-item label="排序"> <el-input v-model="menu.sort"/> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false" size="small">取 消</el-button> <el-button type="primary" @click="saveOrUpdate()" size="small">确 定</el-button> </span> </el-dialog> </div> </template> <script> import menuApi from '@/api/wechat/menu' const defaultForm = { id: null, parentId: 1, name: '', nameId: null, sort: 1, type: 'view', meunKey: '', url: '' } export default { // 定义数据 data() { return { list: [], dialogVisible: false, menu: defaultForm, saveBtnDisabled: false } }, // 当页面加载时获取数据 created() { this.fetchData() }, methods: { // 调用api层获取数据库中的数据 fetchData() { console.log('加载列表') menuApi.findMenuInfo().then(response => { this.list = response.data console.log(this.list) }) }, // 根据id删除数据 removeDataById(id) { // debugger this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // promise // 点击确定,远程调用ajax return menuApi.removeById(id) }).then((response) => { this.fetchData(this.page) this.$message.success(response.message || '删除成功') }).catch(() => { this.$message.info('取消删除') }) }, // ------------- add() { this.dialogVisible = true this.menu = Object.assign({}, defaultForm) }, edit(id) { this.dialogVisible = true this.fetchDataById(id) }, fetchDataById(id) { menuApi.getById(id).then(response => { this.menu = response.data }) }, saveOrUpdate() { this.saveBtnDisabled = true // 防止表单重复提交 if (!this.menu.id) { this.saveData() } else { this.updateData() } }, // 新增 saveData() { menuApi.save(this.menu).then(response => { this.$message.success(response.message || '操作成功') this.dialogVisible = false this.fetchData(this.page) }) }, // 根据id更新记录 updateData() { menuApi.updateById(this.menu).then(response => { this.$message.success(response.message || '操作成功') this.dialogVisible = false this.fetchData(this.page) }) } } } </script>
微信公众号登录:微信公众平台
导入pom依赖:
<!--微信公众号依赖--> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>4.1.0</version> </dependency>
修改配置文件:
配置类:
@Data @Component @ConfigurationProperties(prefix = "wechat") public class WechatAccountConfig { private String mpAppId; private String mpAppSecret; }
@Component public class WeChatMpConfig { @Autowired private WechatAccountConfig wechatAccountConfig; @Bean public WxMpService wxMpService(){ WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxMpConfigStorage()); return wxMpService; } @Bean public WxMpConfigStorage wxMpConfigStorage(){ WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl(); wxMpConfigStorage.setAppId(wechatAccountConfig.getMpAppId()); wxMpConfigStorage.setSecret(wechatAccountConfig.getMpAppSecret()); return wxMpConfigStorage; } }
查询微信菜单:
service层:
@Override public void syncMenu() { List<MenuVo> menuVoList = this.findMenuInfo(); //菜单 JSONArray buttonList = new JSONArray(); for(MenuVo oneMenuVo : menuVoList) { JSONObject one = new JSONObject(); one.put("name", oneMenuVo.getName()); if(CollectionUtils.isEmpty(oneMenuVo.getChildren())) { one.put("type", oneMenuVo.getType()); one.put("url", "http://oa.cjc.cn/#"+oneMenuVo.getUrl()); } else { JSONArray subButton = new JSONArray(); for(MenuVo twoMenuVo : oneMenuVo.getChildren()) { JSONObject view = new JSONObject(); view.put("type", twoMenuVo.getType()); if(twoMenuVo.getType().equals("view")) { view.put("name", twoMenuVo.getName()); //H5页面地址 view.put("url", "http://oa.cjc.cn#"+twoMenuVo.getUrl()); } else { view.put("name", twoMenuVo.getName()); view.put("key", twoMenuVo.getMeunKey()); } subButton.add(view); } one.put("sub_button", subButton); } buttonList.add(one); } //菜单 JSONObject button = new JSONObject(); button.put("button", buttonList); try { wxMpService.getMenuService().menuCreate(button.toJSONString()); } catch (WxErrorException e) { throw new RuntimeException(e); } }
controller层:
@ApiOperation(value = "同步菜单") @GetMapping("syncMenu") public Result createMenu() { wechatMenuService.syncMenu(); return Result.ok(); }
前端代码:
在api/wechat/menu.js添加
syncMenu() { return request({ url: `${api_name}/syncMenu`, method: `get` }) },
list.vue
<el-button class="btn-add" size="mini" @click="syncMenu" >同步菜单</el-button>
syncMenu() { this.$confirm('你确定上传菜单吗, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { return menuApi.syncMenu() }).then((response) => { this.$message.success(response.message) }).catch(error => { console.log('error', error) if (error === 'cancel') { this.$message.info('取消上传') } }) }
删除微信菜单:
service层:
//删除菜单 @Override public void removeMenu() { try { wxMpService.getMenuService().menuDelete(); } catch (WxErrorException e) { throw new RuntimeException(e); } }
controller层:
@ApiOperation(value = "删除菜单") @DeleteMapping("/removeMenu") public Result removeMenu() { wechatMenuService.removeMenu(); return Result.ok(); }
前端代码:
在api/wechat/menu.js添加
removeMenu() { return request({ url: `${api_name}/removeMenu`, method: `delete` }) }
list.vue
<el-button class="btn-add" size="mini" @click="removeMenu">删除菜单</el-button>
removeMenu() { menuApi.removeMenu().then(response => { this.$message.success('菜单已删除') }) }
1.2、微信授权登录
内网穿透:
访问地址:https://ngrok.cc/login/register
😒、实名认证
😊、开通隧道
开通隧道
配置域名:
修改yml配置文件
后端代码:
controller层
@Controller @RequestMapping("/admin/wechat") public class WechatController { @Autowired private SysUserService sysUserService; @Autowired private WxMpService wxMpService; @Value("${wechat.userInfoUrl}") private String userInfoUrl; @GetMapping("/authorize") public String authorize(@RequestParam("returnUrl") String returnUrl, HttpServletRequest request) { /** * userInfoUrl:授权路径 * WxConsts.OAuth2Scope.SNSAPI_USERINFO:固定值,授权类型 * URLEncoder.encode(returnUrl.replace("guiguoa","#")):授权成功跳转路径 */ String url = null; try { url = wxMpService.getOAuth2Service() .buildAuthorizationUrl(userInfoUrl, WxConsts.OAuth2Scope.SNSAPI_USERINFO, URLEncoder.encode(returnUrl.replace("oacjc", "#"),"utf-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } return "redirect:" + url; } @GetMapping("/userInfo") public String userInfo(@RequestParam("code") String code, @RequestParam("state") String returnUrl) throws Exception { //获取accessToken WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code); //使用 accessToken获取openId String openId = accessToken.getOpenId(); //获取微信用户信息 WxOAuth2UserInfo userInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, null); LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SysUser::getOpenId,openId); SysUser user = sysUserService.getOne(wrapper); String token = ""; if (user != null){ token = JwtHelper.createToken(user.getId(),user.getUsername()); } if(returnUrl.indexOf("?") == -1) { return "redirect:" + returnUrl + "?token=" + token + "&openId=" + openId; } else { return "redirect:" + returnUrl + "&token=" + token + "&openId=" + openId; } } @ApiOperation(value = "微信账号绑定手机") @PostMapping("/bindPhone") @ResponseBody public Result bindPhone(@RequestBody BindPhoneVo bindPhoneVo) { //根据手机号查询 LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SysUser::getPhone,bindPhoneVo.getPhone()); SysUser user = sysUserService.getOne(wrapper); //用户存在 if (user != null){ user.setPhone(bindPhoneVo.getPhone()); sysUserService.updateById(user); String token = JwtHelper.createToken(user.getId(),user.getUsername()); return Result.ok(); }else { return Result.fail("手机号不存在"); } } }
配置权限:
/** * 配置哪些请求不拦截 * 排除swagger相关请求 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/admin/modeler/**","/diagram-viewer/**","/editor-app/**","/*.html", "/admin/processImage/**", "/admin/wechat/authorize","/admin/wechat/userInfo","/admin/wechat/bindPhone", "/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html"); }
前端代码:
request.js
import axios from "axios"; // 创建axios实例 const service = axios.create({ baseURL: "http://oacjc.vipgz1.91tunnel.com", // api 的 base_url timeout: 30000 // 请求超时时间 }); // http request 拦截器 service.interceptors.request.use(config => { let token = window.localStorage.getItem("token") || ""; if (token != "") { config.headers["token"] = token; } return config; }, err => { return Promise.reject(err); }); // http response 拦截器 service.interceptors.response.use(response => { if (response.data.code == 208) { // debugger // 替换# 后台获取不到#后面的参数 let url = window.location.href.replace('#', 'guiguoa') window.location = 'http://oacjc.vipgz1.91tunnel.com/admin/wechat/authorize?returnUrl=' + url } else { if (response.data.code == 200) { return response.data; } else { // 209没有权限 系统会自动跳转授权登录的,已在App.vue处理过,不需要提示 if (response.data.code != 209) { alert(response.data.message || "error"); } return Promise.reject(response); } } }, error => { return Promise.reject(error.response); // 返回接口返回的错误信息 }); export default service;
app.vue
<template> <div id="app"> <router-view /> <el-dialog title="绑定手机" :visible.sync="dialogVisible" width="80%" > <el-form ref="dataForm" :model="bindPhoneVo" size="small"> <h4>绑定你的手机号,建立云尚办公系统关联关系</h4> <el-form-item label="手机号码"> <el-input v-model="bindPhoneVo.phone"/> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button type="primary" icon="el-icon-check" @click="saveBind()" size="small">确 定</el-button> </span> </el-dialog> </div> </template> <script> import userInfoApi from '@/api/userInfo' export default { data() { return { show: true, dialogVisible: false, bindPhoneVo: { openId: '', phone: '' } }; }, created() { // 处理微信授权登录 this.wechatLogin(); }, methods: { wechatLogin() { // 处理微信授权登录 let token = this.getQueryString('token') || ''; let openId = this.getQueryString('openId') || ''; // token === '' && openId != '' 只要这种情况,未绑定账号 if(token === '' && openId != '') { // 绑定账号 this.bindPhoneVo.openId = openId this.dialogVisible = true } else { // 如果绑定了,授权登录直接返回token if(token !== '') { window.localStorage.setItem('token', token); } token = window.localStorage.getItem('token') || ''; if (token == '') { let url = window.location.href.replace('#', 'guiguoa') window.location = 'http://oacjc.vipgz1.91tunnel.com/admin/wechat/authorize?returnUrl=' + url } } }, saveBind() { if(this.bindPhoneVo.phone.length != 11) { alert('手机号码格式不正确') return } userInfoApi.bindPhone(this.bindPhoneVo).then(response => { window.localStorage.setItem('token', response.data); this.dialogVisible = false window.location = ' http://ggkt1.vipgz1.91tunnel.com' }) }, getQueryString (paramName) { if(window.location.href.indexOf('?') == -1) return ''; let searchString = window.location.href.split('?')[1]; let i, val, params = searchString.split("&"); for (i=0;i<params.length;i++) { val = params[i].split("="); if (val[0] == paramName) { return val[1]; } } return ''; } } }; </script> <style lang="scss"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; } </style>
问题1:
解决办法
问题2:
解决办法
1.3、消息推送
@Service
public class MessageServiceImpl implements MessageService {
@Autowired
private OaProcessService oaProcessService;
@Autowired
private SysUserService sysUserService;
@Autowired
private OaProcessTemplateService oaProcessTemplateService;
@Autowired
private WxMpService wxMpService;
//推送待审批人
@Override
public void pushPendingMessage(Long processId, Long userId, String taskId) {
//查询流程表
Process process = oaProcessService.getById(processId);
//查询要推荐人的信息
SysUser sysUser = sysUserService.getById(userId);
//查询审批模板信息
ProcessTemplate processTemplate = oaProcessTemplateService.getById(process.getProcessTemplateId());
//查询提交审批人的信息
SysUser submitUser = sysUserService.getById(process.getUserId());
//获取要给的消息人的openid
String openId = sysUser.getOpenId();
if (!StringUtils.hasLength(openId)){
openId = "oRsNl6CP_c3VJy9zGi6mtwQw8Rbo";
}
//设置消息发送信息
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
.toUser(openId)//给谁发消息
.templateId("7lpS5eMTBoIWYgXyiVb3cJgk2I4WdS5YOBEZxNvpLtU")//创建的模板信息的id
.url("http://ggkt1.vipgz1.91tunnel.com/#/show/" + processId + "/" + taskId)
.build();
JSONObject jsonObject = JSON.parseObject(process.getFormValues());
JSONObject formShowData = jsonObject.getJSONObject("formShowData");
StringBuffer content = new StringBuffer();
for (Map.Entry entry : formShowData.entrySet()) {
content.append(entry.getKey()).append(":").append(entry.getValue()).append("\n ");
}
//设置模板里面参数值
templateMessage.addData(new WxMpTemplateData("first",submitUser.getName()+"提交"+processTemplate.getName()+"请查阅","#272727"));
templateMessage.addData(new WxMpTemplateData("keyword1", process.getProcessCode(), "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword2", new DateTime(process.getCreateTime()).toString("yyyy-MM-dd HH:mm:ss"), "#272727"));
templateMessage.addData(new WxMpTemplateData("content", content.toString(), "#272727"));
//消息发送
try {
String msg = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
System.out.println(msg);
} catch (WxErrorException e) {
throw new RuntimeException(e);
}
}
}